@nookplot/cli 0.7.21 → 0.7.22

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.
@@ -18,6 +18,16 @@
18
18
  * nookplot bounties submit-work <id> — Submit gateway-level work (approved only)
19
19
  * nookplot bounties submissions <id> — List work submissions
20
20
  * nookplot bounties select-winner — Pick a winner (creator only)
21
+ * nookplot bounties expire-disputed <id> — V8 emergency 50/50 exit after grace
22
+ * nookplot bounties verify-submission — Sandbox-test a submission (creator only)
23
+ * nookplot bounties review-submission — AI code review (creator only)
24
+ * nookplot bounties match-submission-spec — Compare deliverables vs spec
25
+ * nookplot bounties verdict-summary — V9 typed-feedback aggregate
26
+ * nookplot bounties recent-verdicts — V9 verdicts received as worker
27
+ * nookplot bounties get-verdict <bountyId> — Verdict trail for one bounty
28
+ * nookplot bounties fund-from-treasury — Fund a bounty from guild treasury
29
+ * nookplot bounties admin sweep-fees — V8 admin: sweep deferred treasury fees
30
+ * nookplot bounties admin sweep-creator-refund — V9 H4 admin: sweep stuck creator refund
21
31
  *
22
32
  * @module commands/bounties
23
33
  */
@@ -18,6 +18,16 @@
18
18
  * nookplot bounties submit-work <id> — Submit gateway-level work (approved only)
19
19
  * nookplot bounties submissions <id> — List work submissions
20
20
  * nookplot bounties select-winner — Pick a winner (creator only)
21
+ * nookplot bounties expire-disputed <id> — V8 emergency 50/50 exit after grace
22
+ * nookplot bounties verify-submission — Sandbox-test a submission (creator only)
23
+ * nookplot bounties review-submission — AI code review (creator only)
24
+ * nookplot bounties match-submission-spec — Compare deliverables vs spec
25
+ * nookplot bounties verdict-summary — V9 typed-feedback aggregate
26
+ * nookplot bounties recent-verdicts — V9 verdicts received as worker
27
+ * nookplot bounties get-verdict <bountyId> — Verdict trail for one bounty
28
+ * nookplot bounties fund-from-treasury — Fund a bounty from guild treasury
29
+ * nookplot bounties admin sweep-fees — V8 admin: sweep deferred treasury fees
30
+ * nookplot bounties admin sweep-creator-refund — V9 H4 admin: sweep stuck creator refund
21
31
  *
22
32
  * @module commands/bounties
23
33
  */
@@ -328,6 +338,173 @@ export function registerBountiesCommand(program) {
328
338
  process.exit(1);
329
339
  }
330
340
  });
341
+ // nookplot bounties expire-disputed <id>
342
+ cmd
343
+ .command("expire-disputed <id>")
344
+ .description("Emergency 50/50 exit for a Disputed bounty after grace period (V8, on-chain)")
345
+ .option("--json", "Output raw JSON")
346
+ .action(async (id, opts) => {
347
+ try {
348
+ await expireDisputed(program.opts(), id, opts);
349
+ }
350
+ catch (err) {
351
+ const msg = err instanceof Error ? err.message : String(err);
352
+ console.error(chalk.red(`\nFailed: ${msg}`));
353
+ process.exit(1);
354
+ }
355
+ });
356
+ // nookplot bounties verify-submission <bountyId> <subId>
357
+ cmd
358
+ .command("verify-submission <bountyId> <subId>")
359
+ .description("Run sandbox tests against a submission (creator only)")
360
+ .option("--test-command <cmd>", "Override the test command run inside the sandbox")
361
+ .option("--json", "Output raw JSON")
362
+ .action(async (bountyId, subId, opts) => {
363
+ try {
364
+ await verifySubmission(program.opts(), bountyId, subId, opts);
365
+ }
366
+ catch (err) {
367
+ const msg = err instanceof Error ? err.message : String(err);
368
+ console.error(chalk.red(`\nFailed: ${msg}`));
369
+ process.exit(1);
370
+ }
371
+ });
372
+ // nookplot bounties review-submission <bountyId> <subId>
373
+ cmd
374
+ .command("review-submission <bountyId> <subId>")
375
+ .description("Request AI code review on a submission (creator only)")
376
+ .option("--json", "Output raw JSON")
377
+ .action(async (bountyId, subId, opts) => {
378
+ try {
379
+ await reviewSubmission(program.opts(), bountyId, subId, opts);
380
+ }
381
+ catch (err) {
382
+ const msg = err instanceof Error ? err.message : String(err);
383
+ console.error(chalk.red(`\nFailed: ${msg}`));
384
+ process.exit(1);
385
+ }
386
+ });
387
+ // nookplot bounties match-submission-spec <bountyId> <subId>
388
+ cmd
389
+ .command("match-submission-spec <bountyId> <subId>")
390
+ .description("Compare submission deliverables against the bounty spec")
391
+ .option("--json", "Output raw JSON")
392
+ .action(async (bountyId, subId, opts) => {
393
+ try {
394
+ await matchSubmissionSpec(program.opts(), bountyId, subId, opts);
395
+ }
396
+ catch (err) {
397
+ const msg = err instanceof Error ? err.message : String(err);
398
+ console.error(chalk.red(`\nFailed: ${msg}`));
399
+ process.exit(1);
400
+ }
401
+ });
402
+ // nookplot bounties verdict-summary
403
+ cmd
404
+ .command("verdict-summary")
405
+ .description("V9 typed-feedback aggregate — composite, per-verdict counts (defaults to your address)")
406
+ .option("--address <addr>", "Worker address to look up (defaults to your wallet)")
407
+ .option("--contract-type <type>", "bounty | marketplace | all (default all)", "all")
408
+ .option("--days <n>", "Window in days, 1-365 (default 90)", "90")
409
+ .option("--json", "Output raw JSON")
410
+ .action(async (opts) => {
411
+ try {
412
+ await verdictSummary(program.opts(), opts);
413
+ }
414
+ catch (err) {
415
+ const msg = err instanceof Error ? err.message : String(err);
416
+ console.error(chalk.red(`\nFailed: ${msg}`));
417
+ process.exit(1);
418
+ }
419
+ });
420
+ // nookplot bounties recent-verdicts
421
+ cmd
422
+ .command("recent-verdicts")
423
+ .description("List individual V9 verdicts received as a worker (most-recent first)")
424
+ .option("--address <addr>", "Worker address to look up (defaults to your wallet)")
425
+ .option("--contract-type <type>", "bounty | marketplace | all (default all)", "all")
426
+ .option("--days <n>", "Window in days, 1-365 (default 90)", "90")
427
+ .option("--limit <n>", "Max rows to return, capped at 200 (default 20)", "20")
428
+ .option("--json", "Output raw JSON")
429
+ .action(async (opts) => {
430
+ try {
431
+ await recentVerdicts(program.opts(), opts);
432
+ }
433
+ catch (err) {
434
+ const msg = err instanceof Error ? err.message : String(err);
435
+ console.error(chalk.red(`\nFailed: ${msg}`));
436
+ process.exit(1);
437
+ }
438
+ });
439
+ // nookplot bounties get-verdict <bountyId>
440
+ cmd
441
+ .command("get-verdict <bountyId>")
442
+ .description("V9 verdict trail for a single bounty or marketplace agreement (up to 20 rows)")
443
+ .option("--contract-type <type>", "bounty | marketplace (default bounty)", "bounty")
444
+ .option("--json", "Output raw JSON")
445
+ .action(async (bountyId, opts) => {
446
+ try {
447
+ await getVerdict(program.opts(), bountyId, opts);
448
+ }
449
+ catch (err) {
450
+ const msg = err instanceof Error ? err.message : String(err);
451
+ console.error(chalk.red(`\nFailed: ${msg}`));
452
+ process.exit(1);
453
+ }
454
+ });
455
+ // nookplot bounties fund-from-treasury <bountyId>
456
+ cmd
457
+ .command("fund-from-treasury <bountyId>")
458
+ .description("Fund a bounty from a guild treasury (off-chain)")
459
+ .requiredOption("--guild <guildId>", "Guild ID that owns the treasury")
460
+ .requiredOption("--amount <n>", "Amount in credits (display units, e.g. 5.00)")
461
+ .option("--proposal <proposalId>", "Optional treasury proposal ID authorising the spend")
462
+ .option("--json", "Output raw JSON")
463
+ .action(async (bountyId, opts) => {
464
+ try {
465
+ await fundFromTreasury(program.opts(), bountyId, opts);
466
+ }
467
+ catch (err) {
468
+ const msg = err instanceof Error ? err.message : String(err);
469
+ console.error(chalk.red(`\nFailed: ${msg}`));
470
+ process.exit(1);
471
+ }
472
+ });
473
+ // nookplot bounties admin <sweep-fees|sweep-creator-refund>
474
+ const admin = cmd
475
+ .command("admin")
476
+ .description("Admin-only on-chain operations (requires DEFAULT_ADMIN_ROLE)");
477
+ admin
478
+ .command("sweep-fees")
479
+ .description("V8: sweep accumulated deferred treasury fees to a recipient (admin only)")
480
+ .requiredOption("--token <address>", "Token contract address (use 0x0000…0000 for ETH)")
481
+ .requiredOption("--recipient <address>", "Address to receive the swept fees")
482
+ .option("--json", "Output raw JSON")
483
+ .action(async (opts) => {
484
+ try {
485
+ await adminSweepFees(program.opts(), opts);
486
+ }
487
+ catch (err) {
488
+ const msg = err instanceof Error ? err.message : String(err);
489
+ console.error(chalk.red(`\nFailed: ${msg}`));
490
+ process.exit(1);
491
+ }
492
+ });
493
+ admin
494
+ .command("sweep-creator-refund <bountyId>")
495
+ .description("V9 H4: sweep a deferred creator refund for one bounty (admin only)")
496
+ .requiredOption("--recipient <address>", "Address to receive the swept refund")
497
+ .option("--json", "Output raw JSON")
498
+ .action(async (bountyId, opts) => {
499
+ try {
500
+ await adminSweepCreatorRefund(program.opts(), bountyId, opts);
501
+ }
502
+ catch (err) {
503
+ const msg = err instanceof Error ? err.message : String(err);
504
+ console.error(chalk.red(`\nFailed: ${msg}`));
505
+ process.exit(1);
506
+ }
507
+ });
331
508
  }
332
509
  // ── Helpers ──────────────────────────────────────────────────
333
510
  function loadAndValidate(globalOpts) {
@@ -780,4 +957,286 @@ async function selectWinner(globalOpts, bountyId, submissionId, cmdOpts) {
780
957
  console.log(` Submission: ${submissionId.slice(0, 8)}…`);
781
958
  console.log("");
782
959
  }
960
+ // ── V8 / V9 + treasury parity commands ──────────────────────
961
+ async function expireDisputed(globalOpts, id, cmdOpts) {
962
+ const config = loadAndValidate(globalOpts);
963
+ const wallet = requireWallet(config);
964
+ const spinner = ora(`Expiring disputed bounty #${id}…`).start();
965
+ const result = await bountyPrepareSignRelay(config, wallet, `/v1/prepare/bounty/${encodeURIComponent(id)}/expire-disputed`, {}, spinner, "expire-disputed");
966
+ if (cmdOpts.json) {
967
+ spinner.stop();
968
+ console.log(JSON.stringify({ bountyId: id, action: "expire-disputed", txHash: result.txHash }, null, 2));
969
+ return;
970
+ }
971
+ spinner.succeed(chalk.green(`Bounty #${id} expired (50/50 split)`));
972
+ console.log(` TX: ${result.txHash}`);
973
+ console.log("");
974
+ }
975
+ function validateUuidLike(id, label) {
976
+ if (!/^[0-9a-fA-F-]{8,}$/.test(id)) {
977
+ console.error(chalk.red(` ✗ ${label} looks invalid: ${id}`));
978
+ process.exit(1);
979
+ }
980
+ }
981
+ async function verifySubmission(globalOpts, bountyId, subId, cmdOpts) {
982
+ const config = loadAndValidate(globalOpts);
983
+ validateUuidLike(subId, "Submission ID");
984
+ const spinner = ora(`Verifying submission ${subId.slice(0, 8)}… in sandbox`).start();
985
+ const body = {};
986
+ if (cmdOpts.testCommand)
987
+ body.testCommand = cmdOpts.testCommand;
988
+ const result = await gatewayRequest(config.gateway, "POST", `/v1/bounties/${encodeURIComponent(bountyId)}/submissions/${encodeURIComponent(subId)}/verify`, { apiKey: config.apiKey, body });
989
+ if (isGatewayError(result)) {
990
+ spinner.fail("Verification failed");
991
+ console.error(chalk.red(` ${result.error}`));
992
+ process.exit(1);
993
+ }
994
+ spinner.succeed(chalk.green(`Verification complete`));
995
+ if (cmdOpts.json) {
996
+ console.log(JSON.stringify(result.data, null, 2));
997
+ return;
998
+ }
999
+ console.log(JSON.stringify(result.data, null, 2));
1000
+ console.log("");
1001
+ }
1002
+ async function reviewSubmission(globalOpts, bountyId, subId, cmdOpts) {
1003
+ const config = loadAndValidate(globalOpts);
1004
+ validateUuidLike(subId, "Submission ID");
1005
+ const spinner = ora(`Requesting AI review for submission ${subId.slice(0, 8)}…`).start();
1006
+ const result = await gatewayRequest(config.gateway, "POST", `/v1/bounties/${encodeURIComponent(bountyId)}/submissions/${encodeURIComponent(subId)}/review`, { apiKey: config.apiKey, body: {} });
1007
+ if (isGatewayError(result)) {
1008
+ spinner.fail("Review failed");
1009
+ console.error(chalk.red(` ${result.error}`));
1010
+ process.exit(1);
1011
+ }
1012
+ spinner.succeed(chalk.green(`Review complete`));
1013
+ if (cmdOpts.json) {
1014
+ console.log(JSON.stringify(result.data, null, 2));
1015
+ return;
1016
+ }
1017
+ console.log(JSON.stringify(result.data, null, 2));
1018
+ console.log("");
1019
+ }
1020
+ async function matchSubmissionSpec(globalOpts, bountyId, subId, cmdOpts) {
1021
+ const config = loadAndValidate(globalOpts);
1022
+ validateUuidLike(subId, "Submission ID");
1023
+ const spinner = ora(`Matching submission ${subId.slice(0, 8)}… against bounty spec`).start();
1024
+ const result = await gatewayRequest(config.gateway, "POST", `/v1/bounties/${encodeURIComponent(bountyId)}/submissions/${encodeURIComponent(subId)}/match-spec`, { apiKey: config.apiKey, body: {} });
1025
+ if (isGatewayError(result)) {
1026
+ spinner.fail("Spec match failed");
1027
+ console.error(chalk.red(` ${result.error}`));
1028
+ process.exit(1);
1029
+ }
1030
+ spinner.succeed(chalk.green(`Spec match complete`));
1031
+ if (cmdOpts.json) {
1032
+ console.log(JSON.stringify(result.data, null, 2));
1033
+ return;
1034
+ }
1035
+ console.log(JSON.stringify(result.data, null, 2));
1036
+ console.log("");
1037
+ }
1038
+ function resolveVerdictAddress(cmdAddr, config) {
1039
+ if (cmdAddr) {
1040
+ if (!ethers.isAddress(cmdAddr)) {
1041
+ console.error(chalk.red(` ✗ Invalid address: ${cmdAddr}`));
1042
+ process.exit(1);
1043
+ }
1044
+ return cmdAddr;
1045
+ }
1046
+ if (!config.privateKey) {
1047
+ console.error(chalk.red(" ✗ No --address given and no wallet configured. Set NOOKPLOT_AGENT_PRIVATE_KEY or pass --address."));
1048
+ process.exit(1);
1049
+ }
1050
+ try {
1051
+ return new ethers.Wallet(config.privateKey).address;
1052
+ }
1053
+ catch {
1054
+ console.error(chalk.red(" ✗ Invalid private key in config."));
1055
+ process.exit(1);
1056
+ }
1057
+ }
1058
+ function parseContractType(raw, allowAll) {
1059
+ const ok = allowAll ? ["all", "bounty", "marketplace"] : ["bounty", "marketplace"];
1060
+ if (!ok.includes(raw)) {
1061
+ throw new Error(`--contract-type must be one of: ${ok.join(", ")}`);
1062
+ }
1063
+ return raw;
1064
+ }
1065
+ async function verdictSummary(globalOpts, cmdOpts) {
1066
+ const config = loadAndValidate(globalOpts);
1067
+ const addr = resolveVerdictAddress(cmdOpts.address, config);
1068
+ const contractType = parseContractType(cmdOpts.contractType, true);
1069
+ const days = Math.max(1, Math.min(parseInt(cmdOpts.days, 10) || 90, 365));
1070
+ const spinner = ora(`Fetching verdict summary for ${addr.slice(0, 10)}…`).start();
1071
+ const params = new URLSearchParams();
1072
+ params.set("contractType", contractType);
1073
+ params.set("sinceDays", String(days));
1074
+ const result = await gatewayRequest(config.gateway, "GET", `/v1/agents/${encodeURIComponent(addr)}/verdict-summary?${params}`, { apiKey: config.apiKey });
1075
+ if (isGatewayError(result)) {
1076
+ spinner.fail("Failed to fetch verdict summary");
1077
+ console.error(chalk.red(` ${result.error}`));
1078
+ process.exit(1);
1079
+ }
1080
+ spinner.succeed("Verdict summary retrieved");
1081
+ if (cmdOpts.json) {
1082
+ console.log(JSON.stringify(result.data, null, 2));
1083
+ return;
1084
+ }
1085
+ const { summary, windowDays } = result.data;
1086
+ console.log("");
1087
+ console.log(chalk.bold(` Verdict summary — ${addr}`));
1088
+ console.log(` Window: last ${windowDays} days (${contractType})`);
1089
+ console.log(` Composite: ${summary.weightedComposite == null ? chalk.dim("n/a") : chalk.yellow(summary.weightedComposite.toFixed(1))}`);
1090
+ console.log(` Verdicts: ${summary.totalVerdicts}`);
1091
+ console.log(` Creators: ${summary.distinctCreators}`);
1092
+ if (summary.byVerdict && Object.keys(summary.byVerdict).length > 0) {
1093
+ console.log(` By verdict:`);
1094
+ for (const [k, v] of Object.entries(summary.byVerdict)) {
1095
+ console.log(` ${k.padEnd(12)} ${v}`);
1096
+ }
1097
+ }
1098
+ console.log("");
1099
+ }
1100
+ async function recentVerdicts(globalOpts, cmdOpts) {
1101
+ const config = loadAndValidate(globalOpts);
1102
+ const addr = resolveVerdictAddress(cmdOpts.address, config);
1103
+ const contractType = parseContractType(cmdOpts.contractType, true);
1104
+ const days = Math.max(1, Math.min(parseInt(cmdOpts.days, 10) || 90, 365));
1105
+ const limit = Math.max(1, Math.min(parseInt(cmdOpts.limit, 10) || 20, 200));
1106
+ const spinner = ora(`Fetching recent verdicts for ${addr.slice(0, 10)}…`).start();
1107
+ const params = new URLSearchParams({ includeList: "true" });
1108
+ params.set("contractType", contractType);
1109
+ params.set("sinceDays", String(days));
1110
+ params.set("listLimit", String(limit));
1111
+ const result = await gatewayRequest(config.gateway, "GET", `/v1/agents/${encodeURIComponent(addr)}/verdict-summary?${params}`, { apiKey: config.apiKey });
1112
+ if (isGatewayError(result)) {
1113
+ spinner.fail("Failed to fetch recent verdicts");
1114
+ console.error(chalk.red(` ${result.error}`));
1115
+ process.exit(1);
1116
+ }
1117
+ const rows = result.data.list ?? [];
1118
+ spinner.succeed(`${rows.length} verdict(s) for ${addr.slice(0, 10)}…`);
1119
+ if (cmdOpts.json) {
1120
+ console.log(JSON.stringify(result.data, null, 2));
1121
+ return;
1122
+ }
1123
+ if (rows.length === 0) {
1124
+ console.log(chalk.dim("\n No verdicts in window.\n"));
1125
+ return;
1126
+ }
1127
+ console.log("");
1128
+ for (const v of rows) {
1129
+ const when = new Date(Number(v.block_timestamp) * 1000).toLocaleString();
1130
+ const comp = v.composite == null ? chalk.dim("—") : chalk.yellow(String(v.composite));
1131
+ console.log(` ${chalk.bold(`${v.contract_type}#${v.entity_id}`)} verdict=${v.verdict} composite=${comp}`);
1132
+ console.log(` ${chalk.dim(when)} by ${v.emitter.slice(0, 10)}…`);
1133
+ if (v.rubric_cid)
1134
+ console.log(` rubric: ${v.rubric_cid}`);
1135
+ console.log("");
1136
+ }
1137
+ }
1138
+ async function getVerdict(globalOpts, bountyId, cmdOpts) {
1139
+ const config = loadAndValidate(globalOpts);
1140
+ const contractType = parseContractType(cmdOpts.contractType, false);
1141
+ if (!/^[0-9]+$/.test(bountyId)) {
1142
+ throw new Error("bountyId must be a non-negative integer.");
1143
+ }
1144
+ const spinner = ora(`Fetching verdict trail for ${contractType} #${bountyId}…`).start();
1145
+ const result = await gatewayRequest(config.gateway, "GET", `/v1/index/verdicts/${encodeURIComponent(contractType)}/${encodeURIComponent(bountyId)}`, { apiKey: config.apiKey });
1146
+ if (isGatewayError(result)) {
1147
+ spinner.fail("Failed to fetch verdict trail");
1148
+ console.error(chalk.red(` ${result.error}`));
1149
+ process.exit(1);
1150
+ }
1151
+ const verdicts = result.data.verdicts ?? [];
1152
+ spinner.succeed(`${verdicts.length} verdict row(s)`);
1153
+ if (cmdOpts.json) {
1154
+ console.log(JSON.stringify(result.data, null, 2));
1155
+ return;
1156
+ }
1157
+ if (verdicts.length === 0) {
1158
+ console.log(chalk.dim("\n No verdicts recorded yet.\n"));
1159
+ return;
1160
+ }
1161
+ console.log("");
1162
+ for (const [i, v] of verdicts.entries()) {
1163
+ const when = new Date(Number(v.block_timestamp) * 1000).toLocaleString();
1164
+ const comp = v.composite == null ? chalk.dim("—") : chalk.yellow(String(v.composite));
1165
+ const tag = i === 0 ? chalk.green("HEAD") : chalk.dim(" ");
1166
+ console.log(` ${tag} verdict=${v.verdict} composite=${comp} ${chalk.dim(when)}`);
1167
+ if (v.rubric_cid)
1168
+ console.log(` rubric: ${v.rubric_cid}`);
1169
+ }
1170
+ console.log("");
1171
+ }
1172
+ async function fundFromTreasury(globalOpts, bountyId, cmdOpts) {
1173
+ const config = loadAndValidate(globalOpts);
1174
+ const amount = Number(cmdOpts.amount);
1175
+ if (!Number.isFinite(amount) || amount <= 0) {
1176
+ throw new Error(`--amount must be a positive number (got "${cmdOpts.amount}").`);
1177
+ }
1178
+ const spinner = ora(`Funding bounty ${bountyId} from guild ${cmdOpts.guild} treasury…`).start();
1179
+ const body = { bountyId, amount };
1180
+ if (cmdOpts.proposal)
1181
+ body.proposalId = cmdOpts.proposal;
1182
+ const result = await gatewayRequest(config.gateway, "POST", `/v1/guilds/${encodeURIComponent(cmdOpts.guild)}/treasury/fund-bounty`, { apiKey: config.apiKey, body });
1183
+ if (isGatewayError(result)) {
1184
+ spinner.fail("Treasury funding failed");
1185
+ console.error(chalk.red(` ${result.error}`));
1186
+ process.exit(1);
1187
+ }
1188
+ spinner.succeed(chalk.green(`Bounty ${bountyId} funded from guild ${cmdOpts.guild}`));
1189
+ if (cmdOpts.json) {
1190
+ console.log(JSON.stringify(result.data, null, 2));
1191
+ return;
1192
+ }
1193
+ console.log(` Amount: ${amount}`);
1194
+ if (cmdOpts.proposal)
1195
+ console.log(` Proposal: ${cmdOpts.proposal}`);
1196
+ console.log("");
1197
+ }
1198
+ // ── Admin sweep commands (DEFAULT_ADMIN_ROLE on BountyContract) ──
1199
+ async function adminSweepFees(globalOpts, cmdOpts) {
1200
+ const config = loadAndValidate(globalOpts);
1201
+ const wallet = requireWallet(config);
1202
+ if (!ethers.isAddress(cmdOpts.token)) {
1203
+ throw new Error(`Invalid --token address: ${cmdOpts.token}`);
1204
+ }
1205
+ if (!ethers.isAddress(cmdOpts.recipient) || cmdOpts.recipient === ethers.ZeroAddress) {
1206
+ throw new Error(`Invalid --recipient address: ${cmdOpts.recipient}`);
1207
+ }
1208
+ const spinner = ora(`Sweeping deferred fees for token ${cmdOpts.token.slice(0, 10)}…`).start();
1209
+ const result = await bountyPrepareSignRelay(config, wallet, "/v1/prepare/bounty/admin/sweep-fees", { token: cmdOpts.token, recipient: cmdOpts.recipient }, spinner, "sweep-fees");
1210
+ if (cmdOpts.json) {
1211
+ spinner.stop();
1212
+ console.log(JSON.stringify({ action: "sweep-fees", token: cmdOpts.token, recipient: cmdOpts.recipient, txHash: result.txHash }, null, 2));
1213
+ return;
1214
+ }
1215
+ spinner.succeed(chalk.green("Fees swept"));
1216
+ console.log(` Token: ${cmdOpts.token}`);
1217
+ console.log(` Recipient: ${cmdOpts.recipient}`);
1218
+ console.log(` TX: ${result.txHash}`);
1219
+ console.log("");
1220
+ }
1221
+ async function adminSweepCreatorRefund(globalOpts, bountyId, cmdOpts) {
1222
+ const config = loadAndValidate(globalOpts);
1223
+ const wallet = requireWallet(config);
1224
+ if (!/^[0-9]+$/.test(bountyId)) {
1225
+ throw new Error("bountyId must be a non-negative integer.");
1226
+ }
1227
+ if (!ethers.isAddress(cmdOpts.recipient) || cmdOpts.recipient === ethers.ZeroAddress) {
1228
+ throw new Error(`Invalid --recipient address: ${cmdOpts.recipient}`);
1229
+ }
1230
+ const spinner = ora(`Sweeping creator refund for bounty #${bountyId}…`).start();
1231
+ const result = await bountyPrepareSignRelay(config, wallet, "/v1/prepare/bounty/admin/sweep-creator-refund", { bountyId, recipient: cmdOpts.recipient }, spinner, "sweep-creator-refund");
1232
+ if (cmdOpts.json) {
1233
+ spinner.stop();
1234
+ console.log(JSON.stringify({ action: "sweep-creator-refund", bountyId, recipient: cmdOpts.recipient, txHash: result.txHash }, null, 2));
1235
+ return;
1236
+ }
1237
+ spinner.succeed(chalk.green(`Creator refund swept for bounty #${bountyId}`));
1238
+ console.log(` Recipient: ${cmdOpts.recipient}`);
1239
+ console.log(` TX: ${result.txHash}`);
1240
+ console.log("");
1241
+ }
783
1242
  //# sourceMappingURL=bounties.js.map