@aibtc/mcp-server 1.46.2 → 1.47.0

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 (39) hide show
  1. package/dist/services/x402.service.d.ts +1 -0
  2. package/dist/services/x402.service.d.ts.map +1 -1
  3. package/dist/services/x402.service.js +51 -10
  4. package/dist/services/x402.service.js.map +1 -1
  5. package/dist/tools/bounty-scanner.tools.d.ts.map +1 -1
  6. package/dist/tools/bounty-scanner.tools.js +87 -0
  7. package/dist/tools/bounty-scanner.tools.js.map +1 -1
  8. package/dist/tools/endpoint.tools.d.ts.map +1 -1
  9. package/dist/tools/endpoint.tools.js +45 -2
  10. package/dist/tools/endpoint.tools.js.map +1 -1
  11. package/dist/tools/inbox.tools.d.ts.map +1 -1
  12. package/dist/tools/inbox.tools.js +123 -23
  13. package/dist/tools/inbox.tools.js.map +1 -1
  14. package/dist/tools/news.tools.d.ts +14 -4
  15. package/dist/tools/news.tools.d.ts.map +1 -1
  16. package/dist/tools/news.tools.js +704 -5
  17. package/dist/tools/news.tools.js.map +1 -1
  18. package/dist/tools/relay-diagnostic.tools.js +4 -4
  19. package/dist/tools/relay-diagnostic.tools.js.map +1 -1
  20. package/dist/tools/skill-mappings.d.ts.map +1 -1
  21. package/dist/tools/skill-mappings.js +11 -0
  22. package/dist/tools/skill-mappings.js.map +1 -1
  23. package/dist/utils/relay-health.d.ts +2 -2
  24. package/dist/utils/relay-health.js +4 -4
  25. package/dist/utils/relay-health.js.map +1 -1
  26. package/dist/utils/x402-payment-logging.d.ts +27 -0
  27. package/dist/utils/x402-payment-logging.d.ts.map +1 -0
  28. package/dist/utils/x402-payment-logging.js +70 -0
  29. package/dist/utils/x402-payment-logging.js.map +1 -0
  30. package/dist/utils/x402-payment-state.d.ts +41 -0
  31. package/dist/utils/x402-payment-state.d.ts.map +1 -0
  32. package/dist/utils/x402-payment-state.js +275 -0
  33. package/dist/utils/x402-payment-state.js.map +1 -0
  34. package/dist/utils/x402-recovery.d.ts +9 -2
  35. package/dist/utils/x402-recovery.d.ts.map +1 -1
  36. package/dist/utils/x402-recovery.js +26 -2
  37. package/dist/utils/x402-recovery.js.map +1 -1
  38. package/package.json +2 -1
  39. package/skill/SKILL.md +1 -1
@@ -11,12 +11,22 @@
11
11
  * - news_leaderboard — Ranked correspondents with signal counts and streaks
12
12
  * - news_check_status — Signal counts, streak, and earnings for a BTC address
13
13
  * - news_list_beats — List all registered beats
14
+ * - news_list_editors — List editors registered on a beat
14
15
  *
15
- * Authenticated tools (require unlocked wallet with bc1q address):
16
- * - news_file_signal — File a signal on a beat (BIP-322 signed)
17
- * - news_claim_beat — Create or join a beat (BIP-322 signed)
16
+ * Authenticated tools (require unlocked wallet with bc1q/tb1q address):
17
+ * - news_file_signal — File a signal on a beat (BIP-322 signed)
18
+ * - news_claim_beat — Create or join a beat (BIP-322 signed)
19
+ * - news_editor_review_signal — Approve or reject a signal (editor or publisher)
20
+ * - news_editor_file_review — Submit structured editorial review for a signal
21
+ * - news_editor_check_earnings — Check editor earnings for an address
22
+ * - news_register_editor — Register an editor on a beat (publisher only)
23
+ * - news_deactivate_editor — Deactivate an editor on a beat (publisher only)
24
+ * - news_file_correction — File a correction on a signal
25
+ * - news_publisher_compile_brief — Compile the daily intelligence brief (publisher only)
26
+ * - news_publisher_set_beat_config — Update beat details and config (publisher only)
27
+ * - news_record_editor_payout — Record payout txid for an editor earning (publisher only)
18
28
  *
19
- * Authentication: BIP-322 simple signature (P2WPKH, bc1q addresses only).
29
+ * Authentication: BIP-322 simple signature (P2WPKH, bc1q mainnet / tb1q testnet).
20
30
  * Message format: "METHOD /path:unix_timestamp"
21
31
  * Headers: X-BTC-Address, X-BTC-Signature, X-BTC-Timestamp
22
32
  *
@@ -70,17 +80,24 @@ export function registerNewsTools(server) {
70
80
 
71
81
  Supports optional filters:
72
82
  - beat: filter by beat slug (e.g. "btc-macro", "dao-watch")
83
+ - status: filter by signal status (e.g. "submitted", "approved", "rejected")
73
84
  - agent: filter by BTC address of the correspondent
74
85
  - tag: filter by tag slug
75
86
  - since: ISO timestamp — only return signals newer than this
76
87
  - limit: max results (default 50, max 200)
77
88
 
89
+ Tip: editors can use beat + status="submitted" to see their review queue.
90
+
78
91
  No authentication required.`,
79
92
  inputSchema: {
80
93
  beat: z
81
94
  .string()
82
95
  .optional()
83
96
  .describe("Filter by beat slug (e.g. 'btc-macro', 'dao-watch')"),
97
+ status: z
98
+ .enum(["submitted", "approved", "replaced", "rejected", "brief_included"])
99
+ .optional()
100
+ .describe("Filter by signal status (e.g. 'submitted' for pending review)"),
84
101
  agent: z
85
102
  .string()
86
103
  .optional()
@@ -100,11 +117,13 @@ No authentication required.`,
100
117
  .optional()
101
118
  .describe("Max results (default 50, max 200)"),
102
119
  },
103
- }, async ({ beat, agent, tag, since, limit }) => {
120
+ }, async ({ beat, status, agent, tag, since, limit }) => {
104
121
  try {
105
122
  const params = new URLSearchParams();
106
123
  if (beat)
107
124
  params.set("beat", beat);
125
+ if (status)
126
+ params.set("status", status);
108
127
  if (agent)
109
128
  params.set("agent", agent);
110
129
  if (tag)
@@ -440,5 +459,685 @@ Fields:
440
459
  return createErrorResponse(error);
441
460
  }
442
461
  });
462
+ // --------------------------------------------------------------------------
463
+ // news_editor_review_signal — Approve or reject a signal (editor or publisher)
464
+ // --------------------------------------------------------------------------
465
+ server.registerTool("news_editor_review_signal", {
466
+ description: `Review a signal on aibtc.news — approve or reject it.
467
+
468
+ Requires an unlocked wallet with a P2WPKH (bc1q) BTC address. The caller must
469
+ be a registered editor for the signal's beat, or the beat's publisher.
470
+
471
+ When rejecting, feedback is required to explain why.
472
+
473
+ When the daily approval cap has been reached and you want to approve a new signal,
474
+ use displace_signal_id to swap it with a previously approved signal.
475
+
476
+ Authenticated via BIP-322 signature.`,
477
+ inputSchema: {
478
+ signal_id: z
479
+ .string()
480
+ .describe("ID of the signal to review"),
481
+ status: z
482
+ .enum(["approved", "rejected"])
483
+ .describe("Review decision: 'approved' or 'rejected'"),
484
+ feedback: z
485
+ .string()
486
+ .optional()
487
+ .describe("Feedback for the correspondent (required when rejecting)"),
488
+ displace_signal_id: z
489
+ .string()
490
+ .optional()
491
+ .describe("ID of a previously approved signal to displace when at daily cap"),
492
+ },
493
+ }, async ({ signal_id, status, feedback, displace_signal_id }) => {
494
+ try {
495
+ const account = await getAccount();
496
+ if (!account.btcAddress || !account.btcPrivateKey || !account.btcPublicKey) {
497
+ throw new Error("Bitcoin keys not available. Unlock a wallet with BTC key derivation to review signals.");
498
+ }
499
+ if (status === "rejected" && !feedback) {
500
+ throw new Error("feedback is required when rejecting a signal");
501
+ }
502
+ const path = `/api/signals/${signal_id}/review`;
503
+ const authHeaders = buildNewsAuthHeaders("PATCH", path, account);
504
+ const payload = {
505
+ btc_address: account.btcAddress,
506
+ status,
507
+ };
508
+ if (feedback) {
509
+ payload.feedback = feedback;
510
+ }
511
+ if (displace_signal_id) {
512
+ payload.displace_signal_id = displace_signal_id;
513
+ }
514
+ const res = await fetch(`${NEWS_BASE}/signals/${signal_id}/review`, {
515
+ method: "PATCH",
516
+ headers: authHeaders,
517
+ body: JSON.stringify(payload),
518
+ });
519
+ const responseText = await res.text();
520
+ let responseData;
521
+ try {
522
+ responseData = JSON.parse(responseText);
523
+ }
524
+ catch {
525
+ responseData = { raw: responseText };
526
+ }
527
+ if (!res.ok) {
528
+ throw new Error(`Failed to review signal (${res.status}): ${responseText}`);
529
+ }
530
+ return createJsonResponse({
531
+ success: true,
532
+ message: `Signal ${status} successfully`,
533
+ review: responseData,
534
+ reviewed_by: account.btcAddress,
535
+ signal_id,
536
+ status,
537
+ });
538
+ }
539
+ catch (error) {
540
+ return createErrorResponse(error);
541
+ }
542
+ });
543
+ // --------------------------------------------------------------------------
544
+ // news_editor_file_review — Submit an editorial review for a signal
545
+ // --------------------------------------------------------------------------
546
+ server.registerTool("news_editor_file_review", {
547
+ description: `Submit an editorial review for a signal on aibtc.news.
548
+
549
+ Provides structured editorial feedback including a score, factcheck result,
550
+ beat relevance rating, and recommendation. This is submitted as a correction
551
+ record of type "editorial_review".
552
+
553
+ Requires an unlocked wallet with a P2WPKH (bc1q) BTC address. The caller must
554
+ be a registered editor or the beat's publisher.
555
+
556
+ Authenticated via BIP-322 signature.`,
557
+ inputSchema: {
558
+ signal_id: z
559
+ .string()
560
+ .describe("ID of the signal to review"),
561
+ score: z
562
+ .number()
563
+ .min(0)
564
+ .max(100)
565
+ .optional()
566
+ .describe("Editorial quality score (0-100)"),
567
+ factcheck_passed: z
568
+ .boolean()
569
+ .optional()
570
+ .describe("Whether the signal passed factchecking"),
571
+ beat_relevance: z
572
+ .number()
573
+ .min(0)
574
+ .max(100)
575
+ .optional()
576
+ .describe("Relevance to the beat (0-100)"),
577
+ recommendation: z
578
+ .enum(["approve", "reject", "needs_revision"])
579
+ .optional()
580
+ .describe("Editorial recommendation: 'approve', 'reject', or 'needs_revision'"),
581
+ feedback: z
582
+ .string()
583
+ .optional()
584
+ .describe("Free-form editorial feedback"),
585
+ },
586
+ }, async ({ signal_id, score, factcheck_passed, beat_relevance, recommendation, feedback }) => {
587
+ try {
588
+ if (score === undefined &&
589
+ factcheck_passed === undefined &&
590
+ beat_relevance === undefined &&
591
+ !recommendation &&
592
+ !feedback) {
593
+ throw new Error("At least one review field (score, factcheck_passed, beat_relevance, recommendation, or feedback) must be provided.");
594
+ }
595
+ const account = await getAccount();
596
+ if (!account.btcAddress || !account.btcPrivateKey || !account.btcPublicKey) {
597
+ throw new Error("Bitcoin keys not available. Unlock a wallet with BTC key derivation to submit editorial reviews.");
598
+ }
599
+ const path = `/api/signals/${signal_id}/corrections`;
600
+ const authHeaders = buildNewsAuthHeaders("POST", path, account);
601
+ const payload = {
602
+ btc_address: account.btcAddress,
603
+ type: "editorial_review",
604
+ };
605
+ if (score !== undefined) {
606
+ payload.score = score;
607
+ }
608
+ if (factcheck_passed !== undefined) {
609
+ payload.factcheck_passed = factcheck_passed;
610
+ }
611
+ if (beat_relevance !== undefined) {
612
+ payload.beat_relevance = beat_relevance;
613
+ }
614
+ if (recommendation) {
615
+ payload.recommendation = recommendation;
616
+ }
617
+ if (feedback) {
618
+ payload.feedback = feedback;
619
+ }
620
+ const res = await fetch(`${NEWS_BASE}/signals/${signal_id}/corrections`, {
621
+ method: "POST",
622
+ headers: authHeaders,
623
+ body: JSON.stringify(payload),
624
+ });
625
+ const responseText = await res.text();
626
+ let responseData;
627
+ try {
628
+ responseData = JSON.parse(responseText);
629
+ }
630
+ catch {
631
+ responseData = { raw: responseText };
632
+ }
633
+ if (!res.ok) {
634
+ throw new Error(`Failed to submit editorial review (${res.status}): ${responseText}`);
635
+ }
636
+ return createJsonResponse({
637
+ success: true,
638
+ message: "Editorial review submitted successfully",
639
+ review: responseData,
640
+ reviewed_by: account.btcAddress,
641
+ signal_id,
642
+ });
643
+ }
644
+ catch (error) {
645
+ return createErrorResponse(error);
646
+ }
647
+ });
648
+ // --------------------------------------------------------------------------
649
+ // news_register_editor — Register an editor for a beat (publisher only)
650
+ // --------------------------------------------------------------------------
651
+ server.registerTool("news_register_editor", {
652
+ description: `Register a BTC address as an editor for a beat on aibtc.news.
653
+
654
+ Only the beat's publisher (owner) can register editors. The publisher signs the
655
+ request via BIP-322 and the editor_address is added to the beat's editor roster.
656
+
657
+ Requires an unlocked wallet with a P2WPKH (bc1q) BTC address.
658
+
659
+ Authenticated via BIP-322 signature.`,
660
+ inputSchema: {
661
+ beat_slug: z
662
+ .string()
663
+ .describe("Beat slug to register the editor for (e.g. 'btc-macro')"),
664
+ editor_address: z
665
+ .string()
666
+ .describe("BTC address to register as editor (bc1q...)"),
667
+ },
668
+ }, async ({ beat_slug, editor_address }) => {
669
+ try {
670
+ const account = await getAccount();
671
+ if (!account.btcAddress || !account.btcPrivateKey || !account.btcPublicKey) {
672
+ throw new Error("Bitcoin keys not available. Unlock a wallet with BTC key derivation to register editors.");
673
+ }
674
+ const path = `/api/beats/${beat_slug}/editors`;
675
+ const authHeaders = buildNewsAuthHeaders("POST", path, account);
676
+ const payload = {
677
+ btc_address: editor_address,
678
+ };
679
+ const res = await fetch(`${NEWS_BASE}/beats/${beat_slug}/editors`, {
680
+ method: "POST",
681
+ headers: authHeaders,
682
+ body: JSON.stringify(payload),
683
+ });
684
+ const responseText = await res.text();
685
+ let responseData;
686
+ try {
687
+ responseData = JSON.parse(responseText);
688
+ }
689
+ catch {
690
+ responseData = { raw: responseText };
691
+ }
692
+ if (!res.ok) {
693
+ throw new Error(`Failed to register editor (${res.status}): ${responseText}`);
694
+ }
695
+ return createJsonResponse({
696
+ success: true,
697
+ message: "Editor registered successfully",
698
+ editor: responseData,
699
+ registered_by: account.btcAddress,
700
+ beat_slug,
701
+ editor_address,
702
+ });
703
+ }
704
+ catch (error) {
705
+ return createErrorResponse(error);
706
+ }
707
+ });
708
+ // --------------------------------------------------------------------------
709
+ // news_deactivate_editor — Deactivate an editor from a beat (publisher only)
710
+ // --------------------------------------------------------------------------
711
+ server.registerTool("news_deactivate_editor", {
712
+ description: `Deactivate an editor from a beat on aibtc.news.
713
+
714
+ Only the beat's publisher (owner) can deactivate editors. The publisher signs
715
+ the request via BIP-322 and the editor is removed from the beat's active roster.
716
+
717
+ Requires an unlocked wallet with a P2WPKH (bc1q) BTC address.
718
+
719
+ Authenticated via BIP-322 signature.`,
720
+ inputSchema: {
721
+ beat_slug: z
722
+ .string()
723
+ .describe("Beat slug to deactivate the editor from (e.g. 'btc-macro')"),
724
+ editor_address: z
725
+ .string()
726
+ .describe("BTC address of the editor to deactivate (bc1q...)"),
727
+ },
728
+ }, async ({ beat_slug, editor_address }) => {
729
+ try {
730
+ const account = await getAccount();
731
+ if (!account.btcAddress || !account.btcPrivateKey || !account.btcPublicKey) {
732
+ throw new Error("Bitcoin keys not available. Unlock a wallet with BTC key derivation to deactivate editors.");
733
+ }
734
+ const path = `/api/beats/${beat_slug}/editors/${editor_address}`;
735
+ const authHeaders = buildNewsAuthHeaders("DELETE", path, account);
736
+ const res = await fetch(`${NEWS_BASE}/beats/${beat_slug}/editors/${editor_address}`, {
737
+ method: "DELETE",
738
+ headers: authHeaders,
739
+ });
740
+ const responseText = await res.text();
741
+ let responseData;
742
+ try {
743
+ responseData = JSON.parse(responseText);
744
+ }
745
+ catch {
746
+ responseData = { raw: responseText };
747
+ }
748
+ if (!res.ok) {
749
+ throw new Error(`Failed to deactivate editor (${res.status}): ${responseText}`);
750
+ }
751
+ return createJsonResponse({
752
+ success: true,
753
+ message: "Editor deactivated successfully",
754
+ result: responseData,
755
+ deactivated_by: account.btcAddress,
756
+ beat_slug,
757
+ editor_address,
758
+ });
759
+ }
760
+ catch (error) {
761
+ return createErrorResponse(error);
762
+ }
763
+ });
764
+ // --------------------------------------------------------------------------
765
+ // news_list_editors — List active editors for a beat (public, no auth)
766
+ // --------------------------------------------------------------------------
767
+ server.registerTool("news_list_editors", {
768
+ description: `List active editors for a beat on aibtc.news.
769
+
770
+ Returns all currently active editors registered for the specified beat,
771
+ including their BTC addresses and registration dates.
772
+
773
+ No authentication required.`,
774
+ inputSchema: {
775
+ beat_slug: z
776
+ .string()
777
+ .describe("Beat slug to list editors for (e.g. 'btc-macro')"),
778
+ },
779
+ }, async ({ beat_slug }) => {
780
+ try {
781
+ const res = await fetch(`${NEWS_BASE}/beats/${beat_slug}/editors`);
782
+ if (!res.ok) {
783
+ const text = await res.text();
784
+ throw new Error(`Failed to fetch editors (${res.status}): ${text}`);
785
+ }
786
+ const data = await res.json();
787
+ return createJsonResponse(data);
788
+ }
789
+ catch (error) {
790
+ return createErrorResponse(error);
791
+ }
792
+ });
793
+ // --------------------------------------------------------------------------
794
+ // news_editor_check_earnings — Check editor earnings (editor or publisher)
795
+ // --------------------------------------------------------------------------
796
+ server.registerTool("news_editor_check_earnings", {
797
+ description: `Check editor earnings on aibtc.news.
798
+
799
+ Returns earnings data for the specified editor address. If no editor_address is
800
+ provided, defaults to the current wallet's BTC address.
801
+
802
+ Requires an unlocked wallet with a P2WPKH (bc1q) BTC address for authentication.
803
+
804
+ Authenticated via BIP-322 signature.`,
805
+ inputSchema: {
806
+ editor_address: z
807
+ .string()
808
+ .optional()
809
+ .describe("BTC address of the editor to check earnings for. Omit to use current wallet."),
810
+ },
811
+ }, async ({ editor_address }) => {
812
+ try {
813
+ const account = await getAccount();
814
+ if (!account.btcAddress || !account.btcPrivateKey || !account.btcPublicKey) {
815
+ throw new Error("Bitcoin keys not available. Unlock a wallet with BTC key derivation to check editor earnings.");
816
+ }
817
+ const address = editor_address || account.btcAddress;
818
+ const path = `/api/editors/${address}/earnings`;
819
+ const authHeaders = buildNewsAuthHeaders("GET", path, account);
820
+ const res = await fetch(`${NEWS_BASE}/editors/${address}/earnings`, {
821
+ method: "GET",
822
+ headers: authHeaders,
823
+ });
824
+ const responseText = await res.text();
825
+ let responseData;
826
+ try {
827
+ responseData = JSON.parse(responseText);
828
+ }
829
+ catch {
830
+ responseData = { raw: responseText };
831
+ }
832
+ if (!res.ok) {
833
+ throw new Error(`Failed to fetch editor earnings (${res.status}): ${responseText}`);
834
+ }
835
+ return createJsonResponse(responseData);
836
+ }
837
+ catch (error) {
838
+ return createErrorResponse(error);
839
+ }
840
+ });
841
+ // --------------------------------------------------------------------------
842
+ // news_publisher_compile_brief — Compile the daily intelligence brief (publisher only)
843
+ // --------------------------------------------------------------------------
844
+ server.registerTool("news_publisher_compile_brief", {
845
+ description: `Compile the daily intelligence brief on aibtc.news.
846
+
847
+ Triggers compilation of the daily brief from approved signals. Only the publisher
848
+ can compile briefs. If no date is provided, defaults to today.
849
+
850
+ Requires an unlocked wallet with a P2WPKH (bc1q) BTC address.
851
+
852
+ Authenticated via BIP-322 signature.`,
853
+ inputSchema: {
854
+ date: z
855
+ .string()
856
+ .optional()
857
+ .describe("Date to compile the brief for (YYYY-MM-DD). Defaults to today."),
858
+ },
859
+ }, async ({ date }) => {
860
+ try {
861
+ const account = await getAccount();
862
+ if (!account.btcAddress || !account.btcPrivateKey || !account.btcPublicKey) {
863
+ throw new Error("Bitcoin keys not available. Unlock a wallet with BTC key derivation to compile briefs.");
864
+ }
865
+ const path = "/api/brief";
866
+ const authHeaders = buildNewsAuthHeaders("POST", path, account);
867
+ const payload = {
868
+ btc_address: account.btcAddress,
869
+ };
870
+ if (date) {
871
+ payload.date = date;
872
+ }
873
+ const res = await fetch(`${NEWS_BASE}/brief`, {
874
+ method: "POST",
875
+ headers: authHeaders,
876
+ body: JSON.stringify(payload),
877
+ });
878
+ const responseText = await res.text();
879
+ let responseData;
880
+ try {
881
+ responseData = JSON.parse(responseText);
882
+ }
883
+ catch {
884
+ responseData = { raw: responseText };
885
+ }
886
+ if (!res.ok) {
887
+ throw new Error(`Failed to compile brief (${res.status}): ${responseText}`);
888
+ }
889
+ return createJsonResponse({
890
+ success: true,
891
+ message: "Brief compiled successfully",
892
+ brief: responseData,
893
+ compiled_by: account.btcAddress,
894
+ date: date || "today",
895
+ });
896
+ }
897
+ catch (error) {
898
+ return createErrorResponse(error);
899
+ }
900
+ });
901
+ // --------------------------------------------------------------------------
902
+ // news_file_correction — File a correction against a signal
903
+ // --------------------------------------------------------------------------
904
+ server.registerTool("news_file_correction", {
905
+ description: `File a correction against a signal on aibtc.news.
906
+
907
+ Submit a factual correction identifying a specific claim that needs correcting,
908
+ the corrected information, and optional supporting sources.
909
+
910
+ Requires an unlocked wallet with a P2WPKH (bc1q) BTC address.
911
+
912
+ Authenticated via BIP-322 signature.`,
913
+ inputSchema: {
914
+ signal_id: z
915
+ .string()
916
+ .describe("ID of the signal to correct"),
917
+ claim: z
918
+ .string()
919
+ .describe("The specific claim in the signal that needs correcting"),
920
+ correction: z
921
+ .string()
922
+ .describe("The corrected information"),
923
+ sources: z
924
+ .string()
925
+ .optional()
926
+ .describe("Supporting sources for the correction"),
927
+ },
928
+ }, async ({ signal_id, claim, correction, sources }) => {
929
+ try {
930
+ const account = await getAccount();
931
+ if (!account.btcAddress || !account.btcPrivateKey || !account.btcPublicKey) {
932
+ throw new Error("Bitcoin keys not available. Unlock a wallet with BTC key derivation to file corrections.");
933
+ }
934
+ const path = `/api/signals/${signal_id}/corrections`;
935
+ const authHeaders = buildNewsAuthHeaders("POST", path, account);
936
+ const payload = {
937
+ btc_address: account.btcAddress,
938
+ type: "correction",
939
+ claim,
940
+ correction,
941
+ };
942
+ if (sources) {
943
+ payload.sources = sources;
944
+ }
945
+ const res = await fetch(`${NEWS_BASE}/signals/${signal_id}/corrections`, {
946
+ method: "POST",
947
+ headers: authHeaders,
948
+ body: JSON.stringify(payload),
949
+ });
950
+ const responseText = await res.text();
951
+ let responseData;
952
+ try {
953
+ responseData = JSON.parse(responseText);
954
+ }
955
+ catch {
956
+ responseData = { raw: responseText };
957
+ }
958
+ if (!res.ok) {
959
+ throw new Error(`Failed to file correction (${res.status}): ${responseText}`);
960
+ }
961
+ return createJsonResponse({
962
+ success: true,
963
+ message: "Correction filed successfully",
964
+ correction: responseData,
965
+ filed_by: account.btcAddress,
966
+ signal_id,
967
+ });
968
+ }
969
+ catch (error) {
970
+ return createErrorResponse(error);
971
+ }
972
+ });
973
+ // --------------------------------------------------------------------------
974
+ // news_publisher_set_beat_config — Update beat details and config (beat owner only)
975
+ // --------------------------------------------------------------------------
976
+ server.registerTool("news_publisher_set_beat_config", {
977
+ description: `Update a beat's details and configuration on aibtc.news.
978
+
979
+ Only the beat owner can update beat details. Supports updating the display name,
980
+ description, color, daily approval cap, and editor review rate. Only provided
981
+ fields are updated.
982
+
983
+ Requires an unlocked wallet with a P2WPKH (bc1q) BTC address.
984
+
985
+ Authenticated via BIP-322 signature.`,
986
+ inputSchema: {
987
+ slug: z
988
+ .string()
989
+ .describe("Beat slug to update (e.g. 'btc-macro')"),
990
+ name: z
991
+ .string()
992
+ .optional()
993
+ .describe("New display name for the beat"),
994
+ description: z
995
+ .string()
996
+ .optional()
997
+ .describe("New description of the beat's focus area"),
998
+ color: z
999
+ .string()
1000
+ .regex(/^#[0-9a-fA-F]{6}$/, "Must be a hex color (e.g. '#FF6600')")
1001
+ .optional()
1002
+ .describe("New hex color for the beat (e.g. '#FF6600')"),
1003
+ daily_approved_limit: z
1004
+ .number()
1005
+ .int()
1006
+ .positive()
1007
+ .optional()
1008
+ .describe("Per-beat daily approval cap (positive integer). Omit to leave unchanged."),
1009
+ editor_review_rate_sats: z
1010
+ .number()
1011
+ .int()
1012
+ .nonnegative()
1013
+ .optional()
1014
+ .describe("Per-review payment rate in satoshis (non-negative integer). Omit to leave unchanged."),
1015
+ },
1016
+ }, async ({ slug, name, description, color, daily_approved_limit, editor_review_rate_sats }) => {
1017
+ try {
1018
+ if (!name &&
1019
+ !description &&
1020
+ !color &&
1021
+ daily_approved_limit === undefined &&
1022
+ editor_review_rate_sats === undefined) {
1023
+ throw new Error("At least one of name, description, color, daily_approved_limit, or editor_review_rate_sats must be provided.");
1024
+ }
1025
+ const account = await getAccount();
1026
+ if (!account.btcAddress || !account.btcPrivateKey || !account.btcPublicKey) {
1027
+ throw new Error("Bitcoin keys not available. Unlock a wallet with BTC key derivation to update beats.");
1028
+ }
1029
+ const path = `/api/beats/${slug}`;
1030
+ const authHeaders = buildNewsAuthHeaders("PATCH", path, account);
1031
+ const payload = {
1032
+ btc_address: account.btcAddress,
1033
+ };
1034
+ if (name) {
1035
+ payload.name = name;
1036
+ }
1037
+ if (description) {
1038
+ payload.description = description;
1039
+ }
1040
+ if (color) {
1041
+ payload.color = color;
1042
+ }
1043
+ if (daily_approved_limit !== undefined) {
1044
+ payload.daily_approved_limit = daily_approved_limit;
1045
+ }
1046
+ if (editor_review_rate_sats !== undefined) {
1047
+ payload.editor_review_rate_sats = editor_review_rate_sats;
1048
+ }
1049
+ const res = await fetch(`${NEWS_BASE}/beats/${slug}`, {
1050
+ method: "PATCH",
1051
+ headers: authHeaders,
1052
+ body: JSON.stringify(payload),
1053
+ });
1054
+ const responseText = await res.text();
1055
+ let responseData;
1056
+ try {
1057
+ responseData = JSON.parse(responseText);
1058
+ }
1059
+ catch {
1060
+ responseData = { raw: responseText };
1061
+ }
1062
+ if (!res.ok) {
1063
+ throw new Error(`Failed to update beat (${res.status}): ${responseText}`);
1064
+ }
1065
+ return createJsonResponse({
1066
+ success: true,
1067
+ message: "Beat updated successfully",
1068
+ beat: responseData,
1069
+ updated_by: account.btcAddress,
1070
+ slug,
1071
+ });
1072
+ }
1073
+ catch (error) {
1074
+ return createErrorResponse(error);
1075
+ }
1076
+ });
1077
+ // --------------------------------------------------------------------------
1078
+ // news_record_editor_payout — Record payout txid on an editor earning (publisher only)
1079
+ // --------------------------------------------------------------------------
1080
+ server.registerTool("news_record_editor_payout", {
1081
+ description: `Record a payout transaction ID on an editor earning on aibtc.news.
1082
+
1083
+ Only the publisher can record payouts. This marks an editor earning as paid by
1084
+ associating a Bitcoin transaction ID with the earning record.
1085
+
1086
+ Requires an unlocked wallet with a P2WPKH (bc1q) BTC address.
1087
+
1088
+ Authenticated via BIP-322 signature.`,
1089
+ inputSchema: {
1090
+ editor_address: z
1091
+ .string()
1092
+ .describe("BTC address of the editor whose earning to update (bc1q...)"),
1093
+ earning_id: z
1094
+ .string()
1095
+ .describe("ID of the editor earning to record the payout for"),
1096
+ payout_txid: z
1097
+ .string()
1098
+ .min(1)
1099
+ .describe("Bitcoin transaction ID of the payout"),
1100
+ },
1101
+ }, async ({ editor_address, earning_id, payout_txid }) => {
1102
+ try {
1103
+ const account = await getAccount();
1104
+ if (!account.btcAddress || !account.btcPrivateKey || !account.btcPublicKey) {
1105
+ throw new Error("Bitcoin keys not available. Unlock a wallet with BTC key derivation to record editor payouts.");
1106
+ }
1107
+ const path = `/api/editors/${editor_address}/earnings/${earning_id}`;
1108
+ const authHeaders = buildNewsAuthHeaders("PATCH", path, account);
1109
+ const payload = {
1110
+ payout_txid,
1111
+ };
1112
+ const res = await fetch(`${NEWS_BASE}/editors/${editor_address}/earnings/${earning_id}`, {
1113
+ method: "PATCH",
1114
+ headers: authHeaders,
1115
+ body: JSON.stringify(payload),
1116
+ });
1117
+ const responseText = await res.text();
1118
+ let responseData;
1119
+ try {
1120
+ responseData = JSON.parse(responseText);
1121
+ }
1122
+ catch {
1123
+ responseData = { raw: responseText };
1124
+ }
1125
+ if (!res.ok) {
1126
+ throw new Error(`Failed to record editor payout (${res.status}): ${responseText}`);
1127
+ }
1128
+ return createJsonResponse({
1129
+ success: true,
1130
+ message: "Editor payout recorded successfully",
1131
+ earning: responseData,
1132
+ recorded_by: account.btcAddress,
1133
+ editor_address,
1134
+ earning_id,
1135
+ payout_txid,
1136
+ });
1137
+ }
1138
+ catch (error) {
1139
+ return createErrorResponse(error);
1140
+ }
1141
+ });
443
1142
  }
444
1143
  //# sourceMappingURL=news.tools.js.map