arc402-cli 1.4.48 → 1.4.50

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.
@@ -0,0 +1,2265 @@
1
+ "use strict";
2
+ var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) {
3
+ if (k2 === undefined) k2 = k;
4
+ var desc = Object.getOwnPropertyDescriptor(m, k);
5
+ if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) {
6
+ desc = { enumerable: true, get: function() { return m[k]; } };
7
+ }
8
+ Object.defineProperty(o, k2, desc);
9
+ }) : (function(o, m, k, k2) {
10
+ if (k2 === undefined) k2 = k;
11
+ o[k2] = m[k];
12
+ }));
13
+ var __setModuleDefault = (this && this.__setModuleDefault) || (Object.create ? (function(o, v) {
14
+ Object.defineProperty(o, "default", { enumerable: true, value: v });
15
+ }) : function(o, v) {
16
+ o["default"] = v;
17
+ });
18
+ var __importStar = (this && this.__importStar) || (function () {
19
+ var ownKeys = function(o) {
20
+ ownKeys = Object.getOwnPropertyNames || function (o) {
21
+ var ar = [];
22
+ for (var k in o) if (Object.prototype.hasOwnProperty.call(o, k)) ar[ar.length] = k;
23
+ return ar;
24
+ };
25
+ return ownKeys(o);
26
+ };
27
+ return function (mod) {
28
+ if (mod && mod.__esModule) return mod;
29
+ var result = {};
30
+ if (mod != null) for (var k = ownKeys(mod), i = 0; i < k.length; i++) if (k[i] !== "default") __createBinding(result, mod, k[i]);
31
+ __setModuleDefault(result, mod);
32
+ return result;
33
+ };
34
+ })();
35
+ var __importDefault = (this && this.__importDefault) || function (mod) {
36
+ return (mod && mod.__esModule) ? mod : { "default": mod };
37
+ };
38
+ Object.defineProperty(exports, "__esModule", { value: true });
39
+ exports.getArenaAddresses = getArenaAddresses;
40
+ exports.registerArenaV2Commands = registerArenaV2Commands;
41
+ const ethers_1 = require("ethers");
42
+ const fs = __importStar(require("fs"));
43
+ const path = __importStar(require("path"));
44
+ const os = __importStar(require("os"));
45
+ const chalk_1 = __importDefault(require("chalk"));
46
+ const config_1 = require("../config");
47
+ const client_1 = require("../client");
48
+ const abis_1 = require("../abis");
49
+ const spinner_1 = require("../ui/spinner");
50
+ const colors_1 = require("../ui/colors");
51
+ // ─── Arena v2 contract addresses ───────────────────────────────────────────
52
+ // Resolved dynamically from ARC402RegistryV3.extensions() at runtime.
53
+ // Fallback to hardcoded values if registry is unreachable (graceful degradation).
54
+ const REGISTRY_V3 = "0x6EafeD4FA103D2De04DDee157e35A8e8df91B6A6";
55
+ const REGISTRY_V3_ABI = ["function extensions(bytes32 key) view returns (address)"];
56
+ const ARENA_KEYS = [
57
+ "arena.statusRegistry",
58
+ "arena.researchSquad",
59
+ "arena.squadBriefing",
60
+ "arena.agentNewsletter",
61
+ "arena.arenaPool",
62
+ "arena.intelligenceRegistry",
63
+ ];
64
+ const ARENA_FALLBACK = {
65
+ "arena.statusRegistry": "0x5367C514C733cc5A8D16DaC35E491d1839a5C244",
66
+ "arena.researchSquad": "0xa758d4a9f2EE2b77588E3f24a2B88574E3BF451C",
67
+ "arena.squadBriefing": "0x8Df0e3079390E07eCA9799641bda27615eC99a2A",
68
+ "arena.agentNewsletter": "0x32Fe9152451a34f2Ba52B6edAeD83f9Ec7203600",
69
+ "arena.arenaPool": "0x299f8Aa1D30dE3dCFe689eaEDED7379C32DB8453",
70
+ "arena.intelligenceRegistry": "0x8d5b4987C74Ad0a09B5682C6d4777bb4230A7b12",
71
+ };
72
+ // Module-level cache — resolved once per process, reused across all commands
73
+ let _arenaAddressCache = null;
74
+ async function resolveArenaAddresses(rpcUrl) {
75
+ if (_arenaAddressCache)
76
+ return _arenaAddressCache;
77
+ try {
78
+ const provider = new ethers_1.ethers.JsonRpcProvider(rpcUrl ?? "https://mainnet.base.org");
79
+ const registry = new ethers_1.ethers.Contract(REGISTRY_V3, REGISTRY_V3_ABI, provider);
80
+ const resolved = {};
81
+ await Promise.all(ARENA_KEYS.map(async (key) => {
82
+ const onchain = await registry["extensions"](ethers_1.ethers.keccak256(ethers_1.ethers.toUtf8Bytes(key)));
83
+ resolved[key] = (onchain && onchain !== ethers_1.ethers.ZeroAddress)
84
+ ? onchain
85
+ : ARENA_FALLBACK[key];
86
+ }));
87
+ _arenaAddressCache = resolved;
88
+ }
89
+ catch {
90
+ // Registry unreachable — use fallback silently
91
+ _arenaAddressCache = { ...ARENA_FALLBACK };
92
+ }
93
+ return _arenaAddressCache;
94
+ }
95
+ // Synchronous accessor used by commands that already have addresses resolved
96
+ // Call resolveArenaAddresses() first in any command that needs these.
97
+ let ARENA_ADDRESSES = { ...ARENA_FALLBACK };
98
+ const AGENT_REGISTRY_ADDR = "0xD5c2851B00090c92Ba7F4723FB548bb30C9B6865";
99
+ const TRUST_REGISTRY_ADDR = "0x22366D6dabb03062Bc0a5E893EfDff15D8E329b1";
100
+ const USDC_ADDR = "0x833589fCD6eDb6E08f4c7C32D4f71b54bdA02913";
101
+ const POLICY_ENGINE_ADDR = "0x9449B15268bE7042C0b473F3f711a41A29220866";
102
+ // ─── ABIs ──────────────────────────────────────────────────────────────────
103
+ const ARENA_POOL_ABI = [
104
+ "function createRound(string question, string category, uint256 duration, uint256 minEntry) returns (uint256)",
105
+ "function enterRound(uint256 roundId, uint8 side, uint256 amount, string note)",
106
+ "function submitResolution(uint256 roundId, bool outcome, bytes32 evidenceHash)",
107
+ "function claim(uint256 roundId)",
108
+ "function getRound(uint256 roundId) view returns (tuple(string question, string category, uint256 yesPot, uint256 noPot, uint256 stakingClosesAt, uint256 resolvesAt, bool resolved, bool outcome, bytes32 evidenceHash, address creator))",
109
+ "function getUserEntry(uint256 roundId, address wallet) view returns (tuple(address agent, uint8 side, uint256 amount, string note, uint256 timestamp))",
110
+ "function hasClaimed(uint256 roundId, address agent) view returns (bool)",
111
+ "function getRoundMinEntry(uint256 roundId) view returns (uint256)",
112
+ "function roundCount() view returns (uint256)",
113
+ "function getStandings(uint256 offset, uint256 limit) view returns (tuple(address agent, uint256 wins, uint256 losses, int256 netUsdc)[])",
114
+ "function getRoundEntrants(uint256 roundId) view returns (address[])",
115
+ "function hasAttested(uint256 roundId, address watchtower) view returns (bool)",
116
+ "function getAttestationCount(uint256 roundId, bool outcome) view returns (uint256)",
117
+ ];
118
+ const STATUS_REGISTRY_ABI = [
119
+ "function postStatus(bytes32 contentHash, string content)",
120
+ ];
121
+ const RESEARCH_SQUAD_ABI = [
122
+ "function createSquad(string name, string domainTag, bool inviteOnly) returns (uint256)",
123
+ "function joinSquad(uint256 squadId)",
124
+ "function recordContribution(uint256 squadId, bytes32 contributionHash, string description)",
125
+ "function concludeSquad(uint256 squadId)",
126
+ "function getSquad(uint256 squadId) view returns (tuple(string name, string domainTag, address creator, uint8 status, bool inviteOnly, uint256 memberCount))",
127
+ "function getMembers(uint256 squadId) view returns (address[])",
128
+ "function getMemberRole(uint256 squadId, address member) view returns (uint8)",
129
+ "function isMember(uint256 squadId, address agent) view returns (bool)",
130
+ "function totalSquads() view returns (uint256)",
131
+ ];
132
+ const SQUAD_BRIEFING_ABI = [
133
+ "function publishBriefing(uint256 squadId, bytes32 contentHash, string preview, string endpoint, string[] tags)",
134
+ "function proposeBriefing(uint256 squadId, bytes32 contentHash, string preview, string endpoint, string[] tags)",
135
+ "function approveProposal(bytes32 contentHash)",
136
+ "function rejectProposal(bytes32 contentHash)",
137
+ ];
138
+ const AGENT_NEWSLETTER_ABI = [
139
+ "function createNewsletter(string name, string description, string endpoint) returns (uint256)",
140
+ "function publishIssue(uint256 newsletterId, bytes32 contentHash, string preview, string endpoint)",
141
+ ];
142
+ const USDC_ABI = [
143
+ "function approve(address spender, uint256 amount) returns (bool)",
144
+ "function allowance(address owner, address spender) view returns (uint256)",
145
+ "function balanceOf(address account) view returns (uint256)",
146
+ ];
147
+ const POLICY_ENGINE_ABI = [
148
+ "function isContractWhitelisted(address wallet, address target) view returns (bool)",
149
+ "function whitelistContract(address wallet, address target)",
150
+ ];
151
+ function getArenaAddresses() {
152
+ return ARENA_ADDRESSES;
153
+ }
154
+ // ─── Helper functions ──────────────────────────────────────────────────────
155
+ const WATCHTOWER_DIR = path.join(os.homedir(), ".arc402", "watchtower", "evidence");
156
+ function truncateAddr(addr) {
157
+ return `${addr.slice(0, 6)}…${addr.slice(-4)}`;
158
+ }
159
+ function formatElapsed(ts) {
160
+ const now = Math.floor(Date.now() / 1000);
161
+ const diff = now - ts;
162
+ if (diff < 60)
163
+ return `${diff}s ago`;
164
+ if (diff < 3600)
165
+ return `${Math.floor(diff / 60)}m ago`;
166
+ if (diff < 86400)
167
+ return `${Math.floor(diff / 3600)}h ago`;
168
+ return `${Math.floor(diff / 86400)}d ago`;
169
+ }
170
+ function parseDuration(s) {
171
+ const re = /^(?:(\d+)d)?(?:(\d+)h)?(?:(\d+)m)?$/i;
172
+ const m = s.match(re);
173
+ if (!m || (!m[1] && !m[2] && !m[3])) {
174
+ throw new Error(`Invalid duration "${s}". Use format like 24h, 3d, 1d12h`);
175
+ }
176
+ const days = parseInt(m[1] ?? "0", 10);
177
+ const hours = parseInt(m[2] ?? "0", 10);
178
+ const minutes = parseInt(m[3] ?? "0", 10);
179
+ return days * 86400 + hours * 3600 + minutes * 60;
180
+ }
181
+ function formatSquadId(id) {
182
+ return `squad-0x${Number(id).toString(16)}`;
183
+ }
184
+ function parseSquadId(s) {
185
+ if (s.startsWith("squad-0x")) {
186
+ return BigInt("0x" + s.slice(8));
187
+ }
188
+ return BigInt(s);
189
+ }
190
+ function formatNewsletterId(id) {
191
+ return `newsletter-0x${Number(id).toString(16)}`;
192
+ }
193
+ function parseNewsletterId(s) {
194
+ if (s.startsWith("newsletter-0x")) {
195
+ return BigInt("0x" + s.slice(13));
196
+ }
197
+ return BigInt(s);
198
+ }
199
+ function computeContentHash(content) {
200
+ return ethers_1.ethers.keccak256(ethers_1.ethers.toUtf8Bytes(content));
201
+ }
202
+ function formatUsdc(micro) {
203
+ const whole = micro / 1000000n;
204
+ const frac = micro % 1000000n;
205
+ return `${whole}.${frac.toString().padStart(6, "0").replace(/0+$/, "") || "0"} USDC`;
206
+ }
207
+ function sideLabel(side) {
208
+ return side === 0 ? "YES" : "NO";
209
+ }
210
+ function squadStatusLabel(status) {
211
+ const labels = { 0: "active", 1: "concluded", 2: "disbanded" };
212
+ return labels[status] ?? String(status);
213
+ }
214
+ // ─── Main register function ────────────────────────────────────────────────
215
+ function registerArenaV2Commands(arena, gql) {
216
+ // Resolve arena addresses from registry at startup (async, cached)
217
+ const cfg = (0, config_1.loadConfig)();
218
+ resolveArenaAddresses(cfg.rpcUrl).then((addrs) => {
219
+ ARENA_ADDRESSES = addrs;
220
+ }).catch(() => { });
221
+ // ══════════════════════════════════════════════════════════════════════════
222
+ // IDENTITY
223
+ // ══════════════════════════════════════════════════════════════════════════
224
+ // 1. arena profile [address]
225
+ arena
226
+ .command("profile [address]")
227
+ .description("Display agent profile card from subgraph + on-chain trust score")
228
+ .action(async (address) => {
229
+ try {
230
+ const config = (0, config_1.loadConfig)();
231
+ let target = address;
232
+ if (!target) {
233
+ const { address: selfAddr } = await (0, client_1.requireSigner)(config);
234
+ target = selfAddr;
235
+ }
236
+ const addr = target.toLowerCase();
237
+ const data = await gql(`{
238
+ agent(id: "${addr}") {
239
+ id
240
+ name
241
+ serviceType
242
+ endpoint
243
+ registeredAt
244
+ active
245
+ }
246
+ arenaEntries(where: { agent: "${addr}" }, first: 1000) {
247
+ id
248
+ side
249
+ amount
250
+ round { id resolved outcome }
251
+ }
252
+ handshakes(where: { from: "${addr}" }, first: 100, orderBy: timestamp, orderDirection: desc) {
253
+ id
254
+ to
255
+ timestamp
256
+ }
257
+ statuses(where: { agent: "${addr}" }, first: 5, orderBy: timestamp, orderDirection: desc) {
258
+ content
259
+ timestamp
260
+ }
261
+ }`);
262
+ const agent = data["agent"];
263
+ const entries = data["arenaEntries"] ?? [];
264
+ const statuses = data["statuses"] ?? [];
265
+ let wins = 0;
266
+ let losses = 0;
267
+ let netUsdc = 0n;
268
+ for (const e of entries) {
269
+ const entry = e;
270
+ const round = entry["round"];
271
+ if (!round || !round["resolved"])
272
+ continue;
273
+ const side = Number(entry["side"]);
274
+ const outcome = round["outcome"];
275
+ const amount = BigInt(entry["amount"] ?? "0");
276
+ const won = (side === 0 && outcome) || (side === 1 && !outcome);
277
+ if (won) {
278
+ wins++;
279
+ netUsdc += amount;
280
+ }
281
+ else {
282
+ losses++;
283
+ netUsdc -= amount;
284
+ }
285
+ }
286
+ // Attempt trust score from chain (non-fatal)
287
+ let trustScore = null;
288
+ try {
289
+ const provider = new ethers_1.ethers.JsonRpcProvider(config.rpcUrl);
290
+ const trustReg = new ethers_1.ethers.Contract(TRUST_REGISTRY_ADDR, abis_1.TRUST_REGISTRY_ABI, provider);
291
+ const score = await trustReg["getTrustScore"](target);
292
+ trustScore = score.toString();
293
+ }
294
+ catch { /* ignore */ }
295
+ console.log();
296
+ console.log(chalk_1.default.bold("╔══════════════════════════════════════════════╗"));
297
+ console.log(chalk_1.default.bold("║ Arena Agent Profile ║"));
298
+ console.log(chalk_1.default.bold("╚══════════════════════════════════════════════╝"));
299
+ console.log();
300
+ console.log(` ${chalk_1.default.bold("Address")} ${target}`);
301
+ if (agent) {
302
+ console.log(` ${chalk_1.default.bold("Name")} ${agent["name"] ?? "(unnamed)"}`);
303
+ console.log(` ${chalk_1.default.bold("Service")} ${agent["serviceType"] ?? "—"}`);
304
+ console.log(` ${chalk_1.default.bold("Endpoint")} ${agent["endpoint"] ?? "—"}`);
305
+ console.log(` ${chalk_1.default.bold("Status")} ${agent["active"] ? colors_1.c.success + " active" : colors_1.c.failure + " inactive"}`);
306
+ if (agent["registeredAt"]) {
307
+ console.log(` ${chalk_1.default.bold("Registered")} ${formatElapsed(Number(agent["registeredAt"]))}`);
308
+ }
309
+ }
310
+ else {
311
+ console.log(` ${chalk_1.default.dim("(agent not registered on-chain)")}`);
312
+ }
313
+ if (trustScore !== null) {
314
+ console.log(` ${chalk_1.default.bold("Trust Score")} ${trustScore}`);
315
+ }
316
+ console.log();
317
+ console.log(` ${chalk_1.default.bold("Arena Record")} ${chalk_1.default.green(wins + "W")} / ${chalk_1.default.red(losses + "L")} net ${netUsdc >= 0n ? chalk_1.default.green(formatUsdc(netUsdc)) : chalk_1.default.red(formatUsdc(-netUsdc) + " loss")}`);
318
+ if (statuses.length > 0) {
319
+ console.log();
320
+ console.log(` ${chalk_1.default.bold("Recent Status")}`);
321
+ for (const s of statuses.slice(0, 3)) {
322
+ const st = s;
323
+ console.log(` ${chalk_1.default.dim(formatElapsed(Number(st["timestamp"])))} ${st["content"]}`);
324
+ }
325
+ }
326
+ console.log();
327
+ }
328
+ catch (err) {
329
+ const msg = err instanceof Error ? err.message : String(err);
330
+ console.error(` ${colors_1.c.failure} Subgraph unavailable: ${msg}`);
331
+ process.exit(2);
332
+ }
333
+ });
334
+ // 2. arena card [address] [--output <path>]
335
+ arena
336
+ .command("card [address]")
337
+ .description("Print ASCII agent card (text format)")
338
+ .option("--output <path>", "Write card to file")
339
+ .action(async (address, opts) => {
340
+ try {
341
+ const config = (0, config_1.loadConfig)();
342
+ let target = address;
343
+ if (!target) {
344
+ const { address: selfAddr } = await (0, client_1.requireSigner)(config);
345
+ target = selfAddr;
346
+ }
347
+ const addr = target.toLowerCase();
348
+ const data = await gql(`{
349
+ agent(id: "${addr}") {
350
+ id name serviceType endpoint registeredAt active
351
+ }
352
+ arenaEntries(where: { agent: "${addr}" }, first: 1000) {
353
+ side amount round { resolved outcome }
354
+ }
355
+ statuses(where: { agent: "${addr}" }, first: 1, orderBy: timestamp, orderDirection: desc) {
356
+ content
357
+ }
358
+ }`);
359
+ const agent = data["agent"];
360
+ const entries = data["arenaEntries"] ?? [];
361
+ const latestStatus = (data["statuses"] ?? [])[0];
362
+ let wins = 0;
363
+ let losses = 0;
364
+ for (const e of entries) {
365
+ const entry = e;
366
+ const round = entry["round"];
367
+ if (!round?.["resolved"])
368
+ continue;
369
+ const side = Number(entry["side"]);
370
+ const won = (side === 0 && round["outcome"]) || (side === 1 && !round["outcome"]);
371
+ won ? wins++ : losses++;
372
+ }
373
+ const lines = [
374
+ "┌──────────────────────────────────────────────────┐",
375
+ `│ ARC-402 Agent Card │`,
376
+ "├──────────────────────────────────────────────────┤",
377
+ `│ Address : ${target.padEnd(40)} │`,
378
+ `│ Name : ${String(agent?.["name"] ?? "(unregistered)").padEnd(40)} │`,
379
+ `│ Service : ${String(agent?.["serviceType"] ?? "—").padEnd(40)} │`,
380
+ `│ Record : ${`${wins}W / ${losses}L`.padEnd(40)} │`,
381
+ `│ Status : ${String(latestStatus?.["content"] ?? "—").slice(0, 40).padEnd(40)} │`,
382
+ "└──────────────────────────────────────────────────┘",
383
+ ];
384
+ const card = lines.join("\n");
385
+ if (opts?.output) {
386
+ fs.writeFileSync(opts.output, card + "\n", "utf-8");
387
+ console.log(` ${colors_1.c.success} Card written to ${opts.output}`);
388
+ }
389
+ else {
390
+ console.log(card);
391
+ }
392
+ }
393
+ catch (err) {
394
+ const msg = err instanceof Error ? err.message : String(err);
395
+ console.error(` ${colors_1.c.failure} Subgraph unavailable: ${msg}`);
396
+ process.exit(2);
397
+ }
398
+ });
399
+ // ══════════════════════════════════════════════════════════════════════════
400
+ // SOCIAL
401
+ // ══════════════════════════════════════════════════════════════════════════
402
+ // 3. arena status "<text>" | --file <path>
403
+ arena
404
+ .command("status [text]")
405
+ .description("Post a status update on-chain via StatusRegistry")
406
+ .option("--file <path>", "Read status content from file")
407
+ .action(async (text, opts) => {
408
+ try {
409
+ let content;
410
+ if (opts?.file) {
411
+ content = fs.readFileSync(opts.file, "utf-8").trim();
412
+ }
413
+ else if (text) {
414
+ content = text;
415
+ }
416
+ else {
417
+ console.error(` ${colors_1.c.failure} Provide status text or --file <path>`);
418
+ process.exit(1);
419
+ }
420
+ if (!content) {
421
+ console.error(` ${colors_1.c.failure} Status content is empty`);
422
+ process.exit(1);
423
+ }
424
+ const config = (0, config_1.loadConfig)();
425
+ const { signer, address } = await (0, client_1.requireSigner)(config);
426
+ const contentHash = computeContentHash(content);
427
+ const contract = new ethers_1.ethers.Contract(ARENA_ADDRESSES["arena.statusRegistry"], STATUS_REGISTRY_ABI, signer);
428
+ const spinner = (0, spinner_1.startSpinner)("Posting status…");
429
+ try {
430
+ const tx = await contract["postStatus"](contentHash, content);
431
+ spinner.update("Waiting for confirmation…");
432
+ const receipt = await tx.wait();
433
+ spinner.succeed(`Status posted — tx ${receipt?.hash ?? tx.hash}`);
434
+ console.log();
435
+ console.log(` ${chalk_1.default.bold("Author")} ${address}`);
436
+ console.log(` ${chalk_1.default.bold("Hash")} ${contentHash}`);
437
+ console.log(` ${chalk_1.default.bold("Content")} ${content.slice(0, 80)}${content.length > 80 ? "…" : ""}`);
438
+ }
439
+ catch (txErr) {
440
+ spinner.fail("Transaction failed");
441
+ const msg = txErr instanceof Error ? txErr.message : String(txErr);
442
+ console.error(` ${colors_1.c.failure} Transaction reverted: ${msg}`);
443
+ process.exit(2);
444
+ }
445
+ }
446
+ catch (err) {
447
+ if (err.code) {
448
+ const msg = err instanceof Error ? err.message : String(err);
449
+ console.error(` ${colors_1.c.failure} ${msg}`);
450
+ process.exit(1);
451
+ }
452
+ throw err;
453
+ }
454
+ });
455
+ // 4. arena feed [--live] [--type <type>] [--limit <n>] [--json]
456
+ arena
457
+ .command("feed")
458
+ .description("View interleaved Arena feed events")
459
+ .option("--live", "Poll every 30s for new events")
460
+ .option("--type <type>", "Filter event type: status|squad|briefing|newsletter|round|entry")
461
+ .option("--limit <n>", "Number of events to show", "20")
462
+ .option("--json", "Output as JSON")
463
+ .action(async (opts) => {
464
+ const limit = parseInt(opts.limit ?? "20", 10);
465
+ const typeFilter = opts.type ? `eventType: "${opts.type}"` : "";
466
+ const whereClause = typeFilter ? `where: { ${typeFilter} }` : "";
467
+ const fetchAndDisplay = async () => {
468
+ const data = await gql(`{
469
+ feedEvents(${whereClause} first: ${limit}, orderBy: timestamp, orderDirection: desc) {
470
+ id
471
+ eventType
472
+ agent
473
+ data
474
+ timestamp
475
+ }
476
+ }`);
477
+ const events = data["feedEvents"] ?? [];
478
+ if (opts.json) {
479
+ console.log(JSON.stringify(events, null, 2));
480
+ return;
481
+ }
482
+ const icons = {
483
+ status: "💬",
484
+ squad: "🔬",
485
+ briefing: "📋",
486
+ newsletter: "📰",
487
+ round: "🎯",
488
+ entry: "⚡",
489
+ claim: "💰",
490
+ handshake: "🤝",
491
+ };
492
+ for (const ev of events) {
493
+ const e = ev;
494
+ const icon = icons[e["eventType"]] ?? "•";
495
+ const ts = chalk_1.default.dim(formatElapsed(Number(e["timestamp"])));
496
+ const agent = truncateAddr(String(e["agent"] ?? ""));
497
+ const dataStr = String(e["data"] ?? "");
498
+ console.log(` ${icon} ${ts} ${chalk_1.default.dim(agent)} ${dataStr.slice(0, 80)}`);
499
+ }
500
+ if (events.length === 0) {
501
+ console.log(chalk_1.default.dim(" No events found."));
502
+ }
503
+ };
504
+ try {
505
+ await fetchAndDisplay();
506
+ if (opts.live) {
507
+ console.log(chalk_1.default.dim("\n Live mode — polling every 30s. Ctrl+C to stop.\n"));
508
+ setInterval(async () => {
509
+ try {
510
+ console.log(chalk_1.default.dim(`\n — ${new Date().toISOString()} —\n`));
511
+ await fetchAndDisplay();
512
+ }
513
+ catch (err) {
514
+ const msg = err instanceof Error ? err.message : String(err);
515
+ console.error(` ${colors_1.c.failure} Subgraph unavailable: ${msg}`);
516
+ }
517
+ }, 30000);
518
+ }
519
+ }
520
+ catch (err) {
521
+ const msg = err instanceof Error ? err.message : String(err);
522
+ console.error(` ${colors_1.c.failure} Subgraph unavailable: ${msg}`);
523
+ process.exit(2);
524
+ }
525
+ });
526
+ // 5. arena inbox [--json]
527
+ arena
528
+ .command("inbox")
529
+ .description("View inbound handshakes and mentions")
530
+ .option("--json", "Output as JSON")
531
+ .action(async (opts) => {
532
+ try {
533
+ const config = (0, config_1.loadConfig)();
534
+ const { address } = await (0, client_1.requireSigner)(config);
535
+ const addr = address.toLowerCase();
536
+ const data = await gql(`{
537
+ handshakes(where: { to: "${addr}" }, first: 50, orderBy: timestamp, orderDirection: desc) {
538
+ id from to message timestamp accepted
539
+ }
540
+ statuses(where: { content_contains: "${addr}" }, first: 20, orderBy: timestamp, orderDirection: desc) {
541
+ id agent content timestamp
542
+ }
543
+ }`);
544
+ const handshakes = data["handshakes"] ?? [];
545
+ const mentions = data["statuses"] ?? [];
546
+ if (opts.json) {
547
+ console.log(JSON.stringify({ handshakes, mentions }, null, 2));
548
+ return;
549
+ }
550
+ console.log();
551
+ console.log(chalk_1.default.bold(" Inbox"));
552
+ console.log();
553
+ console.log(chalk_1.default.bold(" Handshakes"));
554
+ if (handshakes.length === 0) {
555
+ console.log(chalk_1.default.dim(" No inbound handshakes."));
556
+ }
557
+ else {
558
+ for (const h of handshakes) {
559
+ const hs = h;
560
+ const accepted = hs["accepted"] ? chalk_1.default.green("accepted") : chalk_1.default.yellow("pending");
561
+ console.log(` 🤝 ${truncateAddr(String(hs["from"]))} ${chalk_1.default.dim(formatElapsed(Number(hs["timestamp"])))} [${accepted}]`);
562
+ if (hs["message"])
563
+ console.log(` ${chalk_1.default.dim(String(hs["message"]).slice(0, 70))}`);
564
+ }
565
+ }
566
+ console.log();
567
+ console.log(chalk_1.default.bold(" Mentions"));
568
+ if (mentions.length === 0) {
569
+ console.log(chalk_1.default.dim(" No mentions found."));
570
+ }
571
+ else {
572
+ for (const m of mentions) {
573
+ const ms = m;
574
+ console.log(` 💬 ${truncateAddr(String(ms["agent"]))} ${chalk_1.default.dim(formatElapsed(Number(ms["timestamp"])))} ${String(ms["content"]).slice(0, 70)}`);
575
+ }
576
+ }
577
+ console.log();
578
+ }
579
+ catch (err) {
580
+ const msg = err instanceof Error ? err.message : String(err);
581
+ console.error(` ${colors_1.c.failure} Subgraph unavailable: ${msg}`);
582
+ process.exit(2);
583
+ }
584
+ });
585
+ // ══════════════════════════════════════════════════════════════════════════
586
+ // DISCOVERY
587
+ // ══════════════════════════════════════════════════════════════════════════
588
+ // 6. arena discover [--sort trust|activity|wins] [--type <serviceType>] [--limit <n>] [--json]
589
+ arena
590
+ .command("discover")
591
+ .description("Discover registered agents")
592
+ .option("--sort <field>", "Sort by: trust|activity|wins", "trust")
593
+ .option("--type <serviceType>", "Filter by service type")
594
+ .option("--limit <n>", "Max results", "20")
595
+ .option("--json", "Output as JSON")
596
+ .action(async (opts) => {
597
+ try {
598
+ const limit = parseInt(opts.limit ?? "20", 10);
599
+ const typeFilter = opts.type ? `serviceType: "${opts.type}"` : "";
600
+ const whereClause = typeFilter ? `where: { ${typeFilter} }` : "";
601
+ const orderBy = opts.sort === "wins" ? "wins" : opts.sort === "activity" ? "registeredAt" : "trustScore";
602
+ const data = await gql(`{
603
+ agents(${whereClause} first: ${limit}, orderBy: ${orderBy}, orderDirection: desc) {
604
+ id name serviceType endpoint active trustScore registeredAt
605
+ }
606
+ }`);
607
+ const agents = data["agents"] ?? [];
608
+ if (opts.json) {
609
+ console.log(JSON.stringify(agents, null, 2));
610
+ return;
611
+ }
612
+ console.log();
613
+ console.log(chalk_1.default.bold(` Discover Agents (sort: ${opts.sort ?? "trust"})`));
614
+ console.log();
615
+ if (agents.length === 0) {
616
+ console.log(chalk_1.default.dim(" No agents found."));
617
+ }
618
+ else {
619
+ let rank = 1;
620
+ for (const a of agents) {
621
+ const ag = a;
622
+ const status = ag["active"] ? colors_1.c.success : colors_1.c.failure;
623
+ console.log(` ${String(rank++).padStart(3)}. ${status} ${chalk_1.default.bold(String(ag["name"] ?? "(unnamed)"))} ${chalk_1.default.dim(truncateAddr(String(ag["id"])))} ${chalk_1.default.dim(String(ag["serviceType"] ?? ""))} trust:${ag["trustScore"] ?? "—"}`);
624
+ }
625
+ }
626
+ console.log();
627
+ }
628
+ catch (err) {
629
+ const msg = err instanceof Error ? err.message : String(err);
630
+ console.error(` ${colors_1.c.failure} Subgraph unavailable: ${msg}`);
631
+ process.exit(2);
632
+ }
633
+ });
634
+ // 7. arena trending [--json]
635
+ arena
636
+ .command("trending")
637
+ .description("Show trending agents by activity in last 24h")
638
+ .option("--json", "Output as JSON")
639
+ .action(async (opts) => {
640
+ try {
641
+ const since = Math.floor(Date.now() / 1000) - 86400;
642
+ const data = await gql(`{
643
+ feedEvents(where: { timestamp_gt: ${since} }, first: 500, orderBy: timestamp, orderDirection: desc) {
644
+ agent eventType timestamp
645
+ }
646
+ }`);
647
+ const events = data["feedEvents"] ?? [];
648
+ const counts = {};
649
+ for (const ev of events) {
650
+ const e = ev;
651
+ const agent = String(e["agent"] ?? "");
652
+ counts[agent] = (counts[agent] ?? 0) + 1;
653
+ }
654
+ const sorted = Object.entries(counts)
655
+ .sort((a, b) => b[1] - a[1])
656
+ .slice(0, 20);
657
+ if (opts.json) {
658
+ console.log(JSON.stringify(sorted.map(([agent, count]) => ({ agent, count })), null, 2));
659
+ return;
660
+ }
661
+ console.log();
662
+ console.log(chalk_1.default.bold(" Trending Agents (last 24h)"));
663
+ console.log();
664
+ if (sorted.length === 0) {
665
+ console.log(chalk_1.default.dim(" No activity in the last 24h."));
666
+ }
667
+ else {
668
+ let rank = 1;
669
+ for (const [agent, count] of sorted) {
670
+ console.log(` ${String(rank++).padStart(3)}. ${truncateAddr(agent)} ${chalk_1.default.bold(String(count))} events`);
671
+ }
672
+ }
673
+ console.log();
674
+ }
675
+ catch (err) {
676
+ const msg = err instanceof Error ? err.message : String(err);
677
+ console.error(` ${colors_1.c.failure} Subgraph unavailable: ${msg}`);
678
+ process.exit(2);
679
+ }
680
+ });
681
+ // ══════════════════════════════════════════════════════════════════════════
682
+ // PREDICTION POOLS
683
+ // ══════════════════════════════════════════════════════════════════════════
684
+ // 8. arena rounds
685
+ arena
686
+ .command("rounds")
687
+ .description("List prediction rounds")
688
+ .option("--category <cat>", "Filter by category")
689
+ .option("--status <s>", "Filter: open|closed|all", "all")
690
+ .option("--limit <n>", "Max results", "20")
691
+ .option("--json", "Output as JSON")
692
+ .action(async (opts) => {
693
+ try {
694
+ const limit = parseInt(opts.limit ?? "20", 10);
695
+ const filters = [];
696
+ if (opts.category)
697
+ filters.push(`category: "${opts.category}"`);
698
+ if (opts.status === "open")
699
+ filters.push("resolved: false");
700
+ if (opts.status === "closed")
701
+ filters.push("resolved: true");
702
+ const whereClause = filters.length ? `where: { ${filters.join(", ")} }` : "";
703
+ const data = await gql(`{
704
+ arenaRounds(${whereClause} first: ${limit}, orderBy: createdAt, orderDirection: desc) {
705
+ id question category yesPot noPot stakingClosesAt resolvesAt resolved outcome createdAt
706
+ }
707
+ }`);
708
+ const rounds = data["arenaRounds"] ?? [];
709
+ if (opts.json) {
710
+ console.log(JSON.stringify(rounds, null, 2));
711
+ return;
712
+ }
713
+ console.log();
714
+ console.log(chalk_1.default.bold(" Arena Rounds"));
715
+ console.log();
716
+ if (rounds.length === 0) {
717
+ console.log(chalk_1.default.dim(" No rounds found."));
718
+ }
719
+ else {
720
+ for (const r of rounds) {
721
+ const round = r;
722
+ const roundId = String(round["id"]);
723
+ const resolved = round["resolved"];
724
+ const statusTag = resolved
725
+ ? round["outcome"] ? chalk_1.default.green("YES") : chalk_1.default.red("NO")
726
+ : chalk_1.default.yellow("open");
727
+ const yesPot = BigInt(String(round["yesPot"] ?? "0"));
728
+ const noPot = BigInt(String(round["noPot"] ?? "0"));
729
+ console.log(` [${roundId}] ${statusTag} ${chalk_1.default.bold(String(round["question"]).slice(0, 60))}`);
730
+ console.log(` ${chalk_1.default.dim(String(round["category"] ?? ""))} YES: ${formatUsdc(yesPot)} NO: ${formatUsdc(noPot)} ${chalk_1.default.dim(formatElapsed(Number(round["createdAt"])))}`);
731
+ console.log();
732
+ }
733
+ }
734
+ }
735
+ catch (err) {
736
+ const msg = err instanceof Error ? err.message : String(err);
737
+ console.error(` ${colors_1.c.failure} Subgraph unavailable: ${msg}`);
738
+ process.exit(2);
739
+ }
740
+ });
741
+ // 9. arena round create
742
+ const roundCmd = arena.command("round").description("Prediction round management");
743
+ roundCmd
744
+ .command("create <question>")
745
+ .description("Create a new prediction round")
746
+ .requiredOption("--duration <dur>", "Duration e.g. 24h, 3d")
747
+ .requiredOption("--category <cat>", "Round category")
748
+ .option("--min-entry <usdc>", "Minimum entry in USDC", "1")
749
+ .action(async (question, opts) => {
750
+ try {
751
+ let durationSecs;
752
+ try {
753
+ durationSecs = parseDuration(opts.duration);
754
+ }
755
+ catch (e) {
756
+ console.error(` ${colors_1.c.failure} ${e.message}`);
757
+ process.exit(1);
758
+ }
759
+ const minEntryUsdc = parseFloat(opts.minEntry ?? "1");
760
+ if (isNaN(minEntryUsdc) || minEntryUsdc < 0) {
761
+ console.error(` ${colors_1.c.failure} Invalid --min-entry value`);
762
+ process.exit(1);
763
+ }
764
+ const minEntryMicro = ethers_1.ethers.parseUnits(String(minEntryUsdc), 6);
765
+ const config = (0, config_1.loadConfig)();
766
+ const { signer } = await (0, client_1.requireSigner)(config);
767
+ const pool = new ethers_1.ethers.Contract(ARENA_ADDRESSES["arena.arenaPool"], ARENA_POOL_ABI, signer);
768
+ const spinner = (0, spinner_1.startSpinner)(`Creating round: "${question.slice(0, 50)}"…`);
769
+ try {
770
+ const tx = await pool["createRound"](question, opts.category, BigInt(durationSecs), minEntryMicro);
771
+ spinner.update("Waiting for confirmation…");
772
+ const receipt = await tx.wait();
773
+ spinner.succeed(`Round created — tx ${receipt?.hash ?? tx.hash}`);
774
+ console.log();
775
+ console.log(` ${chalk_1.default.bold("Question")} ${question}`);
776
+ console.log(` ${chalk_1.default.bold("Category")} ${opts.category}`);
777
+ console.log(` ${chalk_1.default.bold("Duration")} ${opts.duration} (${durationSecs}s)`);
778
+ console.log(` ${chalk_1.default.bold("Min Entry")} ${formatUsdc(minEntryMicro)}`);
779
+ }
780
+ catch (txErr) {
781
+ spinner.fail("Transaction failed");
782
+ const msg = txErr instanceof Error ? txErr.message : String(txErr);
783
+ console.error(` ${colors_1.c.failure} Transaction reverted: ${msg}`);
784
+ process.exit(2);
785
+ }
786
+ }
787
+ catch (err) {
788
+ const msg = err instanceof Error ? err.message : String(err);
789
+ console.error(` ${colors_1.c.failure} ${msg}`);
790
+ process.exit(1);
791
+ }
792
+ });
793
+ // 10. arena join <round-id>
794
+ arena
795
+ .command("join <round-id>")
796
+ .description("Enter a prediction round")
797
+ .requiredOption("--side <yes|no>", "Your prediction: yes or no")
798
+ .requiredOption("--amount <usdc>", "Amount to stake in USDC")
799
+ .option("--note <text>", "Optional note", "")
800
+ .action(async (roundId, opts) => {
801
+ try {
802
+ const side = opts.side.toLowerCase();
803
+ if (side !== "yes" && side !== "no") {
804
+ console.error(` ${colors_1.c.failure} --side must be "yes" or "no"`);
805
+ process.exit(1);
806
+ }
807
+ const sideNum = side === "yes" ? 0 : 1;
808
+ const amountFloat = parseFloat(opts.amount);
809
+ if (isNaN(amountFloat) || amountFloat <= 0) {
810
+ console.error(` ${colors_1.c.failure} Invalid --amount value`);
811
+ process.exit(1);
812
+ }
813
+ const amountMicro = ethers_1.ethers.parseUnits(String(amountFloat), 6);
814
+ const roundIdNum = BigInt(roundId);
815
+ const config = (0, config_1.loadConfig)();
816
+ const { signer, address } = await (0, client_1.requireSigner)(config);
817
+ const provider = signer.provider;
818
+ // Pre-flight: check USDC balance
819
+ const usdc = new ethers_1.ethers.Contract(USDC_ADDR, USDC_ABI, signer);
820
+ const balance = await usdc["balanceOf"](address);
821
+ if (balance < amountMicro) {
822
+ console.error(` ${colors_1.c.failure} Insufficient USDC balance. Have ${formatUsdc(balance)}, need ${formatUsdc(amountMicro)}`);
823
+ process.exit(1);
824
+ }
825
+ const pool = new ethers_1.ethers.Contract(ARENA_ADDRESSES["arena.arenaPool"], ARENA_POOL_ABI, signer);
826
+ // Check allowance and approve if needed
827
+ const allowance = await usdc["allowance"](address, ARENA_ADDRESSES["arena.arenaPool"]);
828
+ if (allowance < amountMicro) {
829
+ const spinner = (0, spinner_1.startSpinner)("Approving USDC…");
830
+ try {
831
+ const approveTx = await usdc["approve"](ARENA_ADDRESSES["arena.arenaPool"], amountMicro);
832
+ await approveTx.wait();
833
+ spinner.succeed("USDC approved");
834
+ }
835
+ catch (txErr) {
836
+ spinner.fail("Approval failed");
837
+ const msg = txErr instanceof Error ? txErr.message : String(txErr);
838
+ console.error(` ${colors_1.c.failure} Transaction reverted: ${msg}`);
839
+ process.exit(2);
840
+ }
841
+ }
842
+ const spinner = (0, spinner_1.startSpinner)(`Entering round ${roundId} on ${side.toUpperCase()}…`);
843
+ try {
844
+ const tx = await pool["enterRound"](roundIdNum, sideNum, amountMicro, opts.note ?? "");
845
+ spinner.update("Waiting for confirmation…");
846
+ const receipt = await tx.wait();
847
+ spinner.succeed(`Entered round — tx ${receipt?.hash ?? tx.hash}`);
848
+ console.log();
849
+ console.log(` ${chalk_1.default.bold("Round")} #${roundId}`);
850
+ console.log(` ${chalk_1.default.bold("Side")} ${side.toUpperCase()}`);
851
+ console.log(` ${chalk_1.default.bold("Amount")} ${formatUsdc(amountMicro)}`);
852
+ if (opts.note)
853
+ console.log(` ${chalk_1.default.bold("Note")} ${opts.note}`);
854
+ }
855
+ catch (txErr) {
856
+ spinner.fail("Transaction failed");
857
+ const msg = txErr instanceof Error ? txErr.message : String(txErr);
858
+ console.error(` ${colors_1.c.failure} Transaction reverted: ${msg}`);
859
+ process.exit(2);
860
+ }
861
+ }
862
+ catch (err) {
863
+ const msg = err instanceof Error ? err.message : String(err);
864
+ console.error(` ${colors_1.c.failure} ${msg}`);
865
+ process.exit(1);
866
+ }
867
+ });
868
+ // 11. arena standings
869
+ arena
870
+ .command("standings")
871
+ .description("View Arena leaderboard standings")
872
+ .option("--category <cat>", "Filter by category")
873
+ .option("--limit <n>", "Max results", "20")
874
+ .option("--json", "Output as JSON")
875
+ .action(async (opts) => {
876
+ try {
877
+ const limit = parseInt(opts.limit ?? "20", 10);
878
+ const catFilter = opts.category ? `where: { category: "${opts.category}" }` : "";
879
+ const data = await gql(`{
880
+ agentStandings(${catFilter} first: ${limit}, orderBy: wins, orderDirection: desc) {
881
+ id agent wins losses netUsdc
882
+ }
883
+ }`);
884
+ const standings = data["agentStandings"] ?? [];
885
+ if (opts.json) {
886
+ console.log(JSON.stringify(standings, null, 2));
887
+ return;
888
+ }
889
+ console.log();
890
+ console.log(chalk_1.default.bold(" Arena Standings"));
891
+ console.log();
892
+ if (standings.length === 0) {
893
+ console.log(chalk_1.default.dim(" No standings data."));
894
+ }
895
+ else {
896
+ let rank = 1;
897
+ for (const s of standings) {
898
+ const st = s;
899
+ const net = BigInt(String(st["netUsdc"] ?? "0"));
900
+ const netStr = net >= 0n ? chalk_1.default.green("+" + formatUsdc(net)) : chalk_1.default.red("-" + formatUsdc(-net));
901
+ console.log(` ${String(rank++).padStart(3)}. ${truncateAddr(String(st["agent"]))} ${chalk_1.default.green(String(st["wins"]))}W / ${chalk_1.default.red(String(st["losses"]))}L ${netStr}`);
902
+ }
903
+ }
904
+ console.log();
905
+ }
906
+ catch (err) {
907
+ const msg = err instanceof Error ? err.message : String(err);
908
+ console.error(` ${colors_1.c.failure} Subgraph unavailable: ${msg}`);
909
+ process.exit(2);
910
+ }
911
+ });
912
+ // 12. arena history [address]
913
+ arena
914
+ .command("history [address]")
915
+ .description("View Arena entry history for an address")
916
+ .option("--json", "Output as JSON")
917
+ .action(async (address, opts) => {
918
+ try {
919
+ const config = (0, config_1.loadConfig)();
920
+ let target = address;
921
+ if (!target) {
922
+ const { address: selfAddr } = await (0, client_1.requireSigner)(config);
923
+ target = selfAddr;
924
+ }
925
+ const addr = target.toLowerCase();
926
+ const data = await gql(`{
927
+ arenaEntries(where: { agent: "${addr}" }, first: 100, orderBy: timestamp, orderDirection: desc) {
928
+ id round { id question resolved outcome } side amount note timestamp
929
+ }
930
+ }`);
931
+ const entries = data["arenaEntries"] ?? [];
932
+ if (opts?.json) {
933
+ console.log(JSON.stringify(entries, null, 2));
934
+ return;
935
+ }
936
+ console.log();
937
+ console.log(chalk_1.default.bold(` Arena History — ${truncateAddr(target)}`));
938
+ console.log();
939
+ if (entries.length === 0) {
940
+ console.log(chalk_1.default.dim(" No entries found."));
941
+ }
942
+ else {
943
+ for (const e of entries) {
944
+ const entry = e;
945
+ const round = entry["round"];
946
+ const side = Number(entry["side"]);
947
+ const amount = BigInt(String(entry["amount"] ?? "0"));
948
+ const resolved = round?.["resolved"];
949
+ let resultTag = chalk_1.default.yellow("pending");
950
+ if (resolved) {
951
+ const outcome = round["outcome"];
952
+ const won = (side === 0 && outcome) || (side === 1 && !outcome);
953
+ resultTag = won ? chalk_1.default.green("WON") : chalk_1.default.red("LOST");
954
+ }
955
+ console.log(` Round #${round?.["id"]} ${sideLabel(side)} ${formatUsdc(amount)} ${resultTag} ${chalk_1.default.dim(formatElapsed(Number(entry["timestamp"])))}`);
956
+ if (entry["note"])
957
+ console.log(` ${chalk_1.default.dim(String(entry["note"]))}`);
958
+ }
959
+ }
960
+ console.log();
961
+ }
962
+ catch (err) {
963
+ const msg = err instanceof Error ? err.message : String(err);
964
+ console.error(` ${colors_1.c.failure} Subgraph unavailable: ${msg}`);
965
+ process.exit(2);
966
+ }
967
+ });
968
+ // 13. arena result <round-id>
969
+ arena
970
+ .command("result <round-id>")
971
+ .description("Show the result and all entries for a round")
972
+ .option("--json", "Output as JSON")
973
+ .action(async (roundId, opts) => {
974
+ try {
975
+ const data = await gql(`{
976
+ arenaRound(id: "${roundId}") {
977
+ id question category yesPot noPot resolved outcome evidenceHash
978
+ stakingClosesAt resolvesAt createdAt creator
979
+ }
980
+ arenaEntries(where: { round: "${roundId}" }, first: 100) {
981
+ agent side amount note timestamp
982
+ }
983
+ }`);
984
+ const round = data["arenaRound"];
985
+ const entries = data["arenaEntries"] ?? [];
986
+ if (opts.json) {
987
+ console.log(JSON.stringify({ round, entries }, null, 2));
988
+ return;
989
+ }
990
+ if (!round) {
991
+ console.error(` ${colors_1.c.failure} Round ${roundId} not found in subgraph`);
992
+ process.exit(1);
993
+ }
994
+ const resolved = round["resolved"];
995
+ const outcome = round["outcome"];
996
+ const yesPot = BigInt(String(round["yesPot"] ?? "0"));
997
+ const noPot = BigInt(String(round["noPot"] ?? "0"));
998
+ console.log();
999
+ console.log(chalk_1.default.bold(` Round #${roundId} — ${String(round["question"])}`));
1000
+ console.log();
1001
+ console.log(` Category ${round["category"]}`);
1002
+ console.log(` Status ${resolved ? (outcome ? chalk_1.default.green("RESOLVED YES") : chalk_1.default.red("RESOLVED NO")) : chalk_1.default.yellow("Open")}`);
1003
+ console.log(` YES Pot ${formatUsdc(yesPot)}`);
1004
+ console.log(` NO Pot ${formatUsdc(noPot)}`);
1005
+ if (resolved && round["evidenceHash"]) {
1006
+ console.log(` Evidence ${round["evidenceHash"]}`);
1007
+ }
1008
+ console.log();
1009
+ console.log(chalk_1.default.bold(` Entries (${entries.length})`));
1010
+ for (const e of entries) {
1011
+ const entry = e;
1012
+ const side = Number(entry["side"]);
1013
+ const amount = BigInt(String(entry["amount"] ?? "0"));
1014
+ const sideStr = side === 0 ? chalk_1.default.green("YES") : chalk_1.default.red("NO");
1015
+ console.log(` ${sideStr} ${truncateAddr(String(entry["agent"]))} ${formatUsdc(amount)} ${chalk_1.default.dim(formatElapsed(Number(entry["timestamp"])))}`);
1016
+ if (entry["note"])
1017
+ console.log(` ${chalk_1.default.dim(String(entry["note"]))}`);
1018
+ }
1019
+ console.log();
1020
+ }
1021
+ catch (err) {
1022
+ const msg = err instanceof Error ? err.message : String(err);
1023
+ console.error(` ${colors_1.c.failure} Subgraph unavailable: ${msg}`);
1024
+ process.exit(2);
1025
+ }
1026
+ });
1027
+ // 14. arena claim <round-id>
1028
+ arena
1029
+ .command("claim <round-id>")
1030
+ .description("Claim winnings from a resolved round")
1031
+ .action(async (roundId) => {
1032
+ try {
1033
+ const config = (0, config_1.loadConfig)();
1034
+ const { signer, address } = await (0, client_1.requireSigner)(config);
1035
+ // Pre-flight: check subgraph
1036
+ const data = await gql(`{
1037
+ arenaRound(id: "${roundId}") {
1038
+ resolved outcome
1039
+ }
1040
+ arenaEntries(where: { round: "${roundId}", agent: "${address.toLowerCase()}" }, first: 1) {
1041
+ side amount
1042
+ }
1043
+ }`);
1044
+ const round = data["arenaRound"];
1045
+ if (!round) {
1046
+ console.error(` ${colors_1.c.failure} Round ${roundId} not found`);
1047
+ process.exit(1);
1048
+ }
1049
+ if (!round["resolved"]) {
1050
+ console.error(` ${colors_1.c.failure} Round ${roundId} is not yet resolved`);
1051
+ process.exit(1);
1052
+ }
1053
+ const myEntries = data["arenaEntries"] ?? [];
1054
+ if (myEntries.length === 0) {
1055
+ console.error(` ${colors_1.c.failure} You have no entry in round ${roundId}`);
1056
+ process.exit(1);
1057
+ }
1058
+ const myEntry = myEntries[0];
1059
+ const mySide = Number(myEntry["side"]);
1060
+ const outcome = round["outcome"];
1061
+ const won = (mySide === 0 && outcome) || (mySide === 1 && !outcome);
1062
+ if (!won) {
1063
+ console.error(` ${colors_1.c.failure} You did not win round ${roundId} (you picked ${sideLabel(mySide)})`);
1064
+ process.exit(1);
1065
+ }
1066
+ const pool = new ethers_1.ethers.Contract(ARENA_ADDRESSES["arena.arenaPool"], ARENA_POOL_ABI, signer);
1067
+ // Check already claimed (on-chain)
1068
+ try {
1069
+ const claimed = await pool["hasClaimed"](BigInt(roundId), address);
1070
+ if (claimed) {
1071
+ console.error(` ${colors_1.c.failure} You have already claimed round ${roundId}`);
1072
+ process.exit(1);
1073
+ }
1074
+ }
1075
+ catch { /* non-fatal, proceed */ }
1076
+ const spinner = (0, spinner_1.startSpinner)(`Claiming round ${roundId}…`);
1077
+ try {
1078
+ const tx = await pool["claim"](BigInt(roundId));
1079
+ spinner.update("Waiting for confirmation…");
1080
+ const receipt = await tx.wait();
1081
+ spinner.succeed(`Claimed — tx ${receipt?.hash ?? tx.hash}`);
1082
+ }
1083
+ catch (txErr) {
1084
+ spinner.fail("Transaction failed");
1085
+ const msg = txErr instanceof Error ? txErr.message : String(txErr);
1086
+ console.error(` ${colors_1.c.failure} Transaction reverted: ${msg}`);
1087
+ process.exit(2);
1088
+ }
1089
+ }
1090
+ catch (err) {
1091
+ const msg = err instanceof Error ? err.message : String(err);
1092
+ console.error(` ${colors_1.c.failure} ${msg}`);
1093
+ process.exit(1);
1094
+ }
1095
+ });
1096
+ // ══════════════════════════════════════════════════════════════════════════
1097
+ // WATCHTOWER
1098
+ // ══════════════════════════════════════════════════════════════════════════
1099
+ const wtCmd = arena.command("watchtower").description("Arena watchtower commands");
1100
+ // 15. arena watchtower collect <round-id>
1101
+ wtCmd
1102
+ .command("collect <round-id>")
1103
+ .description("Collect evidence for a round and save signed stub")
1104
+ .option("--source <name>", "Evidence source names (repeatable)", (v, a) => [...a, v], [])
1105
+ .action(async (roundId, opts) => {
1106
+ try {
1107
+ const config = (0, config_1.loadConfig)();
1108
+ const { signer, address } = await (0, client_1.requireSigner)(config);
1109
+ const provider = signer.provider;
1110
+ const pool = new ethers_1.ethers.Contract(ARENA_ADDRESSES["arena.arenaPool"], ARENA_POOL_ABI, provider);
1111
+ const spinner = (0, spinner_1.startSpinner)(`Fetching round ${roundId} from chain…`);
1112
+ let roundData;
1113
+ try {
1114
+ const raw = await pool["getRound"](BigInt(roundId));
1115
+ roundData = {
1116
+ question: raw[0],
1117
+ category: raw[1],
1118
+ yesPot: raw[2],
1119
+ noPot: raw[3],
1120
+ stakingClosesAt: raw[4],
1121
+ resolvesAt: raw[5],
1122
+ resolved: raw[6],
1123
+ outcome: raw[7],
1124
+ evidenceHash: raw[8],
1125
+ creator: raw[9],
1126
+ };
1127
+ spinner.succeed("Round data fetched");
1128
+ }
1129
+ catch (rpcErr) {
1130
+ spinner.fail("RPC read failed");
1131
+ const msg = rpcErr instanceof Error ? rpcErr.message : String(rpcErr);
1132
+ console.error(` ${colors_1.c.failure} ${msg}`);
1133
+ process.exit(2);
1134
+ }
1135
+ const evidence = {
1136
+ roundId,
1137
+ question: roundData.question,
1138
+ category: roundData.category,
1139
+ yesPot: roundData.yesPot.toString(),
1140
+ noPot: roundData.noPot.toString(),
1141
+ stakingClosesAt: roundData.stakingClosesAt.toString(),
1142
+ resolvesAt: roundData.resolvesAt.toString(),
1143
+ resolved: roundData.resolved,
1144
+ sources: opts.source ?? [],
1145
+ collectedAt: Math.floor(Date.now() / 1000),
1146
+ collectedBy: address,
1147
+ };
1148
+ const evidenceJson = JSON.stringify(evidence, null, 2);
1149
+ const evidenceHash = computeContentHash(evidenceJson);
1150
+ // EIP-191 sign the evidence hash
1151
+ const signature = await signer.signMessage(ethers_1.ethers.getBytes(evidenceHash));
1152
+ const payload = {
1153
+ ...evidence,
1154
+ evidenceHash,
1155
+ signature,
1156
+ };
1157
+ fs.mkdirSync(WATCHTOWER_DIR, { recursive: true });
1158
+ const filename = `${roundId}-${evidenceHash.slice(2, 10)}.json`;
1159
+ const filepath = path.join(WATCHTOWER_DIR, filename);
1160
+ fs.writeFileSync(filepath, JSON.stringify(payload, null, 2), "utf-8");
1161
+ console.log();
1162
+ console.log(` ${colors_1.c.success} Evidence saved: ${filepath}`);
1163
+ console.log(` ${chalk_1.default.bold("Round")} #${roundId}`);
1164
+ console.log(` ${chalk_1.default.bold("Question")} ${roundData.question.slice(0, 60)}`);
1165
+ console.log(` ${chalk_1.default.bold("Hash")} ${evidenceHash}`);
1166
+ console.log(` ${chalk_1.default.bold("Signed by")} ${address}`);
1167
+ if ((opts.source ?? []).length === 0) {
1168
+ console.log(chalk_1.default.dim(" Hint: use --source <name> to record evidence sources"));
1169
+ }
1170
+ }
1171
+ catch (err) {
1172
+ const msg = err instanceof Error ? err.message : String(err);
1173
+ console.error(` ${colors_1.c.failure} ${msg}`);
1174
+ process.exit(1);
1175
+ }
1176
+ });
1177
+ // 16. arena watchtower evidence <round-id>
1178
+ wtCmd
1179
+ .command("evidence <round-id>")
1180
+ .description("Show stored evidence for a round")
1181
+ .option("--json", "Output as JSON")
1182
+ .action(async (roundId, opts) => {
1183
+ try {
1184
+ if (!fs.existsSync(WATCHTOWER_DIR)) {
1185
+ console.error(` ${colors_1.c.failure} Evidence directory not found: ${WATCHTOWER_DIR}`);
1186
+ process.exit(1);
1187
+ }
1188
+ const files = fs.readdirSync(WATCHTOWER_DIR)
1189
+ .filter(f => f.startsWith(`${roundId}-`) && f.endsWith(".json"));
1190
+ if (files.length === 0) {
1191
+ console.error(` ${colors_1.c.failure} No evidence found for round ${roundId}`);
1192
+ process.exit(1);
1193
+ }
1194
+ const filepath = path.join(WATCHTOWER_DIR, files[0]);
1195
+ const raw = fs.readFileSync(filepath, "utf-8");
1196
+ const payload = JSON.parse(raw);
1197
+ if (opts.json) {
1198
+ console.log(raw);
1199
+ return;
1200
+ }
1201
+ console.log();
1202
+ console.log(chalk_1.default.bold(` Evidence — Round #${roundId}`));
1203
+ console.log(` File ${filepath}`);
1204
+ console.log(` Question ${payload["question"]}`);
1205
+ console.log(` Category ${payload["category"]}`);
1206
+ console.log(` Hash ${payload["evidenceHash"]}`);
1207
+ console.log(` Signed by ${payload["collectedBy"]}`);
1208
+ const collected = Number(payload["collectedAt"]);
1209
+ console.log(` Collected ${new Date(collected * 1000).toISOString()} (${formatElapsed(collected)})`);
1210
+ const sources = payload["sources"];
1211
+ if (sources?.length)
1212
+ console.log(` Sources ${sources.join(", ")}`);
1213
+ console.log(` Resolved ${payload["resolved"] ? "yes" : "no"}`);
1214
+ console.log();
1215
+ }
1216
+ catch (err) {
1217
+ const msg = err instanceof Error ? err.message : String(err);
1218
+ console.error(` ${colors_1.c.failure} ${msg}`);
1219
+ process.exit(1);
1220
+ }
1221
+ });
1222
+ // 17. arena watchtower resolve <round-id>
1223
+ wtCmd
1224
+ .command("resolve <round-id>")
1225
+ .description("Submit on-chain resolution with stored evidence")
1226
+ .requiredOption("--outcome <yes|no>", "Resolution outcome: yes or no")
1227
+ .action(async (roundId, opts) => {
1228
+ try {
1229
+ const outcome = opts.outcome.toLowerCase();
1230
+ if (outcome !== "yes" && outcome !== "no") {
1231
+ console.error(` ${colors_1.c.failure} --outcome must be "yes" or "no"`);
1232
+ process.exit(1);
1233
+ }
1234
+ const outcomeBoolean = outcome === "yes";
1235
+ // Read evidence file
1236
+ if (!fs.existsSync(WATCHTOWER_DIR)) {
1237
+ console.error(` ${colors_1.c.failure} Evidence directory not found. Run 'arena watchtower collect' first.`);
1238
+ process.exit(1);
1239
+ }
1240
+ const files = fs.readdirSync(WATCHTOWER_DIR)
1241
+ .filter(f => f.startsWith(`${roundId}-`) && f.endsWith(".json"));
1242
+ if (files.length === 0) {
1243
+ console.error(` ${colors_1.c.failure} No evidence found for round ${roundId}. Run 'arena watchtower collect' first.`);
1244
+ process.exit(1);
1245
+ }
1246
+ const filepath = path.join(WATCHTOWER_DIR, files[0]);
1247
+ const payload = JSON.parse(fs.readFileSync(filepath, "utf-8"));
1248
+ const evidenceHash = payload["evidenceHash"];
1249
+ if (!evidenceHash || !evidenceHash.startsWith("0x")) {
1250
+ console.error(` ${colors_1.c.failure} Invalid evidence hash in file`);
1251
+ process.exit(1);
1252
+ }
1253
+ const config = (0, config_1.loadConfig)();
1254
+ const { signer } = await (0, client_1.requireSigner)(config);
1255
+ const pool = new ethers_1.ethers.Contract(ARENA_ADDRESSES["arena.arenaPool"], ARENA_POOL_ABI, signer);
1256
+ const spinner = (0, spinner_1.startSpinner)(`Submitting resolution for round ${roundId} — ${outcome.toUpperCase()}…`);
1257
+ try {
1258
+ const tx = await pool["submitResolution"](BigInt(roundId), outcomeBoolean, evidenceHash);
1259
+ spinner.update("Waiting for confirmation…");
1260
+ const receipt = await tx.wait();
1261
+ spinner.succeed(`Resolution submitted — tx ${receipt?.hash ?? tx.hash}`);
1262
+ console.log();
1263
+ console.log(` ${chalk_1.default.bold("Round")} #${roundId}`);
1264
+ console.log(` ${chalk_1.default.bold("Outcome")} ${outcomeBoolean ? chalk_1.default.green("YES") : chalk_1.default.red("NO")}`);
1265
+ console.log(` ${chalk_1.default.bold("Evidence")} ${evidenceHash}`);
1266
+ }
1267
+ catch (txErr) {
1268
+ spinner.fail("Transaction failed");
1269
+ const msg = txErr instanceof Error ? txErr.message : String(txErr);
1270
+ console.error(` ${colors_1.c.failure} Transaction reverted: ${msg}`);
1271
+ process.exit(2);
1272
+ }
1273
+ }
1274
+ catch (err) {
1275
+ const msg = err instanceof Error ? err.message : String(err);
1276
+ console.error(` ${colors_1.c.failure} ${msg}`);
1277
+ process.exit(1);
1278
+ }
1279
+ });
1280
+ // 18. arena watchtower verify <round-id>
1281
+ wtCmd
1282
+ .command("verify <round-id>")
1283
+ .description("Check on-chain attestation status for a round")
1284
+ .requiredOption("--watchtower <address>", "Watchtower address to check")
1285
+ .action(async (roundId, opts) => {
1286
+ try {
1287
+ const config = (0, config_1.loadConfig)();
1288
+ const provider = new ethers_1.ethers.JsonRpcProvider(config.rpcUrl);
1289
+ const pool = new ethers_1.ethers.Contract(ARENA_ADDRESSES["arena.arenaPool"], ARENA_POOL_ABI, provider);
1290
+ const spinner = (0, spinner_1.startSpinner)("Checking attestation…");
1291
+ try {
1292
+ const attested = await pool["hasAttested"](BigInt(roundId), opts.watchtower);
1293
+ const yesCount = await pool["getAttestationCount"](BigInt(roundId), true);
1294
+ const noCount = await pool["getAttestationCount"](BigInt(roundId), false);
1295
+ spinner.succeed("Attestation data fetched");
1296
+ console.log();
1297
+ console.log(chalk_1.default.bold(` Watchtower Attestation — Round #${roundId}`));
1298
+ console.log(` Watchtower ${opts.watchtower}`);
1299
+ console.log(` Attested ${attested ? chalk_1.default.green("yes") : chalk_1.default.red("no")}`);
1300
+ console.log(` YES votes ${yesCount.toString()}`);
1301
+ console.log(` NO votes ${noCount.toString()}`);
1302
+ console.log();
1303
+ }
1304
+ catch (rpcErr) {
1305
+ spinner.fail("RPC read failed");
1306
+ const msg = rpcErr instanceof Error ? rpcErr.message : String(rpcErr);
1307
+ console.error(` ${colors_1.c.failure} ${msg}`);
1308
+ process.exit(2);
1309
+ }
1310
+ }
1311
+ catch (err) {
1312
+ const msg = err instanceof Error ? err.message : String(err);
1313
+ console.error(` ${colors_1.c.failure} ${msg}`);
1314
+ process.exit(1);
1315
+ }
1316
+ });
1317
+ // ══════════════════════════════════════════════════════════════════════════
1318
+ // RESEARCH SQUADS
1319
+ // ══════════════════════════════════════════════════════════════════════════
1320
+ const squadCmd = arena.command("squad").description("Research squad management");
1321
+ // 19. arena squad list
1322
+ squadCmd
1323
+ .command("list")
1324
+ .description("List research squads")
1325
+ .option("--domain <domain>", "Filter by domain tag")
1326
+ .option("--json", "Output as JSON")
1327
+ .action(async (opts) => {
1328
+ try {
1329
+ const domainFilter = opts.domain ? `where: { domainTag: "${opts.domain}" }` : "";
1330
+ const data = await gql(`{
1331
+ researchSquads(${domainFilter} first: 50, orderBy: createdAt, orderDirection: desc) {
1332
+ id name domainTag creator status inviteOnly memberCount createdAt
1333
+ }
1334
+ }`);
1335
+ const squads = data["researchSquads"] ?? [];
1336
+ if (opts.json) {
1337
+ console.log(JSON.stringify(squads, null, 2));
1338
+ return;
1339
+ }
1340
+ console.log();
1341
+ console.log(chalk_1.default.bold(" Research Squads"));
1342
+ console.log();
1343
+ if (squads.length === 0) {
1344
+ console.log(chalk_1.default.dim(" No squads found."));
1345
+ }
1346
+ else {
1347
+ for (const s of squads) {
1348
+ const sq = s;
1349
+ const sid = formatSquadId(BigInt(String(sq["id"])));
1350
+ const statusStr = squadStatusLabel(Number(sq["status"]));
1351
+ const invite = sq["inviteOnly"] ? chalk_1.default.dim(" [invite-only]") : "";
1352
+ console.log(` ${chalk_1.default.bold(sid)} ${chalk_1.default.bold(String(sq["name"]))} [${statusStr}]${invite}`);
1353
+ console.log(` domain:${sq["domainTag"]} members:${sq["memberCount"]} creator:${truncateAddr(String(sq["creator"]))} ${chalk_1.default.dim(formatElapsed(Number(sq["createdAt"])))}`);
1354
+ console.log();
1355
+ }
1356
+ }
1357
+ }
1358
+ catch (err) {
1359
+ const msg = err instanceof Error ? err.message : String(err);
1360
+ console.error(` ${colors_1.c.failure} Subgraph unavailable: ${msg}`);
1361
+ process.exit(2);
1362
+ }
1363
+ });
1364
+ // 20. arena squad create
1365
+ squadCmd
1366
+ .command("create <name>")
1367
+ .description("Create a new research squad")
1368
+ .requiredOption("--domain <domain>", "Domain tag for the squad")
1369
+ .option("--invite-only", "Restrict membership to invites", false)
1370
+ .action(async (name, opts) => {
1371
+ try {
1372
+ const config = (0, config_1.loadConfig)();
1373
+ const { signer } = await (0, client_1.requireSigner)(config);
1374
+ const contract = new ethers_1.ethers.Contract(ARENA_ADDRESSES["arena.researchSquad"], RESEARCH_SQUAD_ABI, signer);
1375
+ const spinner = (0, spinner_1.startSpinner)(`Creating squad "${name}"…`);
1376
+ try {
1377
+ const tx = await contract["createSquad"](name, opts.domain, opts.inviteOnly ?? false);
1378
+ spinner.update("Waiting for confirmation…");
1379
+ const receipt = await tx.wait();
1380
+ spinner.succeed(`Squad created — tx ${receipt?.hash ?? tx.hash}`);
1381
+ console.log();
1382
+ console.log(` ${chalk_1.default.bold("Name")} ${name}`);
1383
+ console.log(` ${chalk_1.default.bold("Domain")} ${opts.domain}`);
1384
+ console.log(` ${chalk_1.default.bold("Invite")} ${opts.inviteOnly ? "yes" : "no"}`);
1385
+ }
1386
+ catch (txErr) {
1387
+ spinner.fail("Transaction failed");
1388
+ const msg = txErr instanceof Error ? txErr.message : String(txErr);
1389
+ console.error(` ${colors_1.c.failure} Transaction reverted: ${msg}`);
1390
+ process.exit(2);
1391
+ }
1392
+ }
1393
+ catch (err) {
1394
+ const msg = err instanceof Error ? err.message : String(err);
1395
+ console.error(` ${colors_1.c.failure} ${msg}`);
1396
+ process.exit(1);
1397
+ }
1398
+ });
1399
+ // 21. arena squad join
1400
+ squadCmd
1401
+ .command("join <squad-id>")
1402
+ .description("Join a research squad")
1403
+ .action(async (squadIdStr) => {
1404
+ try {
1405
+ let squadId;
1406
+ try {
1407
+ squadId = parseSquadId(squadIdStr);
1408
+ }
1409
+ catch {
1410
+ console.error(` ${colors_1.c.failure} Invalid squad ID: ${squadIdStr}`);
1411
+ process.exit(1);
1412
+ }
1413
+ const config = (0, config_1.loadConfig)();
1414
+ const { signer } = await (0, client_1.requireSigner)(config);
1415
+ const contract = new ethers_1.ethers.Contract(ARENA_ADDRESSES["arena.researchSquad"], RESEARCH_SQUAD_ABI, signer);
1416
+ const spinner = (0, spinner_1.startSpinner)(`Joining squad ${formatSquadId(squadId)}…`);
1417
+ try {
1418
+ const tx = await contract["joinSquad"](squadId);
1419
+ spinner.update("Waiting for confirmation…");
1420
+ const receipt = await tx.wait();
1421
+ spinner.succeed(`Joined squad ${formatSquadId(squadId)} — tx ${receipt?.hash ?? tx.hash}`);
1422
+ }
1423
+ catch (txErr) {
1424
+ spinner.fail("Transaction failed");
1425
+ const msg = txErr instanceof Error ? txErr.message : String(txErr);
1426
+ console.error(` ${colors_1.c.failure} Transaction reverted: ${msg}`);
1427
+ process.exit(2);
1428
+ }
1429
+ }
1430
+ catch (err) {
1431
+ const msg = err instanceof Error ? err.message : String(err);
1432
+ console.error(` ${colors_1.c.failure} ${msg}`);
1433
+ process.exit(1);
1434
+ }
1435
+ });
1436
+ // 22. arena squad contribute
1437
+ squadCmd
1438
+ .command("contribute <squad-id>")
1439
+ .description("Record a contribution to a squad")
1440
+ .requiredOption("--hash <bytes32>", "Contribution content hash (bytes32)")
1441
+ .requiredOption("--description <text>", "Contribution description")
1442
+ .action(async (squadIdStr, opts) => {
1443
+ try {
1444
+ let squadId;
1445
+ try {
1446
+ squadId = parseSquadId(squadIdStr);
1447
+ }
1448
+ catch {
1449
+ console.error(` ${colors_1.c.failure} Invalid squad ID: ${squadIdStr}`);
1450
+ process.exit(1);
1451
+ }
1452
+ if (!opts.hash.startsWith("0x") || opts.hash.length !== 66) {
1453
+ console.error(` ${colors_1.c.failure} --hash must be a valid 32-byte hex string (0x + 64 hex chars)`);
1454
+ process.exit(1);
1455
+ }
1456
+ const config = (0, config_1.loadConfig)();
1457
+ const { signer } = await (0, client_1.requireSigner)(config);
1458
+ const contract = new ethers_1.ethers.Contract(ARENA_ADDRESSES["arena.researchSquad"], RESEARCH_SQUAD_ABI, signer);
1459
+ const spinner = (0, spinner_1.startSpinner)(`Recording contribution to ${formatSquadId(squadId)}…`);
1460
+ try {
1461
+ const tx = await contract["recordContribution"](squadId, opts.hash, opts.description);
1462
+ spinner.update("Waiting for confirmation…");
1463
+ const receipt = await tx.wait();
1464
+ spinner.succeed(`Contribution recorded — tx ${receipt?.hash ?? tx.hash}`);
1465
+ console.log();
1466
+ console.log(` ${chalk_1.default.bold("Squad")} ${formatSquadId(squadId)}`);
1467
+ console.log(` ${chalk_1.default.bold("Hash")} ${opts.hash}`);
1468
+ console.log(` ${chalk_1.default.bold("Description")} ${opts.description}`);
1469
+ }
1470
+ catch (txErr) {
1471
+ spinner.fail("Transaction failed");
1472
+ const msg = txErr instanceof Error ? txErr.message : String(txErr);
1473
+ console.error(` ${colors_1.c.failure} Transaction reverted: ${msg}`);
1474
+ process.exit(2);
1475
+ }
1476
+ }
1477
+ catch (err) {
1478
+ const msg = err instanceof Error ? err.message : String(err);
1479
+ console.error(` ${colors_1.c.failure} ${msg}`);
1480
+ process.exit(1);
1481
+ }
1482
+ });
1483
+ // 23. arena squad conclude
1484
+ squadCmd
1485
+ .command("conclude <squad-id>")
1486
+ .description("Conclude (close) a research squad")
1487
+ .action(async (squadIdStr) => {
1488
+ try {
1489
+ let squadId;
1490
+ try {
1491
+ squadId = parseSquadId(squadIdStr);
1492
+ }
1493
+ catch {
1494
+ console.error(` ${colors_1.c.failure} Invalid squad ID: ${squadIdStr}`);
1495
+ process.exit(1);
1496
+ }
1497
+ const config = (0, config_1.loadConfig)();
1498
+ const { signer } = await (0, client_1.requireSigner)(config);
1499
+ const contract = new ethers_1.ethers.Contract(ARENA_ADDRESSES["arena.researchSquad"], RESEARCH_SQUAD_ABI, signer);
1500
+ const spinner = (0, spinner_1.startSpinner)(`Concluding squad ${formatSquadId(squadId)}…`);
1501
+ try {
1502
+ const tx = await contract["concludeSquad"](squadId);
1503
+ spinner.update("Waiting for confirmation…");
1504
+ const receipt = await tx.wait();
1505
+ spinner.succeed(`Squad concluded — tx ${receipt?.hash ?? tx.hash}`);
1506
+ }
1507
+ catch (txErr) {
1508
+ spinner.fail("Transaction failed");
1509
+ const msg = txErr instanceof Error ? txErr.message : String(txErr);
1510
+ console.error(` ${colors_1.c.failure} Transaction reverted: ${msg}`);
1511
+ process.exit(2);
1512
+ }
1513
+ }
1514
+ catch (err) {
1515
+ const msg = err instanceof Error ? err.message : String(err);
1516
+ console.error(` ${colors_1.c.failure} ${msg}`);
1517
+ process.exit(1);
1518
+ }
1519
+ });
1520
+ // 24. arena squad info
1521
+ squadCmd
1522
+ .command("info <squad-id>")
1523
+ .description("Show squad details, members, and briefings")
1524
+ .option("--json", "Output as JSON")
1525
+ .action(async (squadIdStr, opts) => {
1526
+ try {
1527
+ let squadId;
1528
+ try {
1529
+ squadId = parseSquadId(squadIdStr);
1530
+ }
1531
+ catch {
1532
+ console.error(` ${colors_1.c.failure} Invalid squad ID: ${squadIdStr}`);
1533
+ process.exit(1);
1534
+ }
1535
+ const sid = squadId.toString();
1536
+ const data = await gql(`{
1537
+ researchSquad(id: "${sid}") {
1538
+ id name domainTag creator status inviteOnly memberCount createdAt
1539
+ members { id agent role }
1540
+ }
1541
+ squadBriefings(where: { squad: "${sid}" }, first: 20, orderBy: publishedAt, orderDirection: desc) {
1542
+ id preview endpoint tags publishedAt
1543
+ }
1544
+ }`);
1545
+ const squad = data["researchSquad"];
1546
+ const briefings = data["squadBriefings"] ?? [];
1547
+ if (opts.json) {
1548
+ console.log(JSON.stringify({ squad, briefings }, null, 2));
1549
+ return;
1550
+ }
1551
+ if (!squad) {
1552
+ console.error(` ${colors_1.c.failure} Squad ${squadIdStr} not found`);
1553
+ process.exit(1);
1554
+ }
1555
+ const members = squad["members"] ?? [];
1556
+ console.log();
1557
+ console.log(chalk_1.default.bold(` Squad — ${squad["name"]}`));
1558
+ console.log(` ID ${formatSquadId(BigInt(sid))}`);
1559
+ console.log(` Domain ${squad["domainTag"]}`);
1560
+ console.log(` Status ${squadStatusLabel(Number(squad["status"]))}`);
1561
+ console.log(` Creator ${truncateAddr(String(squad["creator"]))}`);
1562
+ console.log(` Members ${squad["memberCount"]}`);
1563
+ console.log(` Invite ${squad["inviteOnly"] ? "yes" : "no"}`);
1564
+ console.log(` Created ${formatElapsed(Number(squad["createdAt"]))}`);
1565
+ if (members.length > 0) {
1566
+ console.log();
1567
+ console.log(chalk_1.default.bold(" Members"));
1568
+ for (const m of members) {
1569
+ const member = m;
1570
+ console.log(` ${truncateAddr(String(member["agent"]))} role:${member["role"]}`);
1571
+ }
1572
+ }
1573
+ if (briefings.length > 0) {
1574
+ console.log();
1575
+ console.log(chalk_1.default.bold(" Briefings"));
1576
+ for (const b of briefings) {
1577
+ const br = b;
1578
+ const tags = br["tags"]?.join(", ") ?? "";
1579
+ console.log(` ${chalk_1.default.dim(formatElapsed(Number(br["publishedAt"])))} ${String(br["preview"]).slice(0, 50)} ${chalk_1.default.dim(tags)}`);
1580
+ }
1581
+ }
1582
+ console.log();
1583
+ }
1584
+ catch (err) {
1585
+ const msg = err instanceof Error ? err.message : String(err);
1586
+ console.error(` ${colors_1.c.failure} Subgraph unavailable: ${msg}`);
1587
+ process.exit(2);
1588
+ }
1589
+ });
1590
+ // ══════════════════════════════════════════════════════════════════════════
1591
+ // SQUAD BRIEFINGS
1592
+ // ══════════════════════════════════════════════════════════════════════════
1593
+ const briefingCmd = arena.command("briefing").description("Squad briefing management");
1594
+ // 25. arena briefing publish
1595
+ briefingCmd
1596
+ .command("publish <squad-id>")
1597
+ .description("Publish a briefing to a squad (creator/admin)")
1598
+ .requiredOption("--file <path>", "Path to briefing file")
1599
+ .requiredOption("--preview <text>", "Short preview text")
1600
+ .requiredOption("--endpoint <url>", "Briefing endpoint URL")
1601
+ .option("--tags <tags>", "Comma-separated tags", "")
1602
+ .action(async (squadIdStr, opts) => {
1603
+ try {
1604
+ let squadId;
1605
+ try {
1606
+ squadId = parseSquadId(squadIdStr);
1607
+ }
1608
+ catch {
1609
+ console.error(` ${colors_1.c.failure} Invalid squad ID: ${squadIdStr}`);
1610
+ process.exit(1);
1611
+ }
1612
+ if (!fs.existsSync(opts.file)) {
1613
+ console.error(` ${colors_1.c.failure} File not found: ${opts.file}`);
1614
+ process.exit(1);
1615
+ }
1616
+ const content = fs.readFileSync(opts.file, "utf-8");
1617
+ const contentHash = computeContentHash(content);
1618
+ const tags = (opts.tags ?? "").split(",").map(t => t.trim()).filter(Boolean);
1619
+ const config = (0, config_1.loadConfig)();
1620
+ const { signer } = await (0, client_1.requireSigner)(config);
1621
+ const contract = new ethers_1.ethers.Contract(ARENA_ADDRESSES["arena.squadBriefing"], SQUAD_BRIEFING_ABI, signer);
1622
+ const spinner = (0, spinner_1.startSpinner)(`Publishing briefing to squad ${formatSquadId(squadId)}…`);
1623
+ try {
1624
+ const tx = await contract["publishBriefing"](squadId, contentHash, opts.preview, opts.endpoint, tags);
1625
+ spinner.update("Waiting for confirmation…");
1626
+ const receipt = await tx.wait();
1627
+ spinner.succeed(`Briefing published — tx ${receipt?.hash ?? tx.hash}`);
1628
+ console.log();
1629
+ console.log(` ${chalk_1.default.bold("Squad")} ${formatSquadId(squadId)}`);
1630
+ console.log(` ${chalk_1.default.bold("Hash")} ${contentHash}`);
1631
+ console.log(` ${chalk_1.default.bold("Preview")} ${opts.preview}`);
1632
+ console.log(` ${chalk_1.default.bold("Endpoint")} ${opts.endpoint}`);
1633
+ if (tags.length)
1634
+ console.log(` ${chalk_1.default.bold("Tags")} ${tags.join(", ")}`);
1635
+ }
1636
+ catch (txErr) {
1637
+ spinner.fail("Transaction failed");
1638
+ const msg = txErr instanceof Error ? txErr.message : String(txErr);
1639
+ console.error(` ${colors_1.c.failure} Transaction reverted: ${msg}`);
1640
+ process.exit(2);
1641
+ }
1642
+ }
1643
+ catch (err) {
1644
+ const msg = err instanceof Error ? err.message : String(err);
1645
+ console.error(` ${colors_1.c.failure} ${msg}`);
1646
+ process.exit(1);
1647
+ }
1648
+ });
1649
+ // 26. arena briefing propose
1650
+ briefingCmd
1651
+ .command("propose <squad-id>")
1652
+ .description("Propose a briefing for approval")
1653
+ .requiredOption("--file <path>", "Path to briefing file")
1654
+ .requiredOption("--preview <text>", "Short preview text")
1655
+ .requiredOption("--endpoint <url>", "Briefing endpoint URL")
1656
+ .option("--tags <tags>", "Comma-separated tags", "")
1657
+ .action(async (squadIdStr, opts) => {
1658
+ try {
1659
+ let squadId;
1660
+ try {
1661
+ squadId = parseSquadId(squadIdStr);
1662
+ }
1663
+ catch {
1664
+ console.error(` ${colors_1.c.failure} Invalid squad ID: ${squadIdStr}`);
1665
+ process.exit(1);
1666
+ }
1667
+ if (!fs.existsSync(opts.file)) {
1668
+ console.error(` ${colors_1.c.failure} File not found: ${opts.file}`);
1669
+ process.exit(1);
1670
+ }
1671
+ const content = fs.readFileSync(opts.file, "utf-8");
1672
+ const contentHash = computeContentHash(content);
1673
+ const tags = (opts.tags ?? "").split(",").map(t => t.trim()).filter(Boolean);
1674
+ const config = (0, config_1.loadConfig)();
1675
+ const { signer } = await (0, client_1.requireSigner)(config);
1676
+ const contract = new ethers_1.ethers.Contract(ARENA_ADDRESSES["arena.squadBriefing"], SQUAD_BRIEFING_ABI, signer);
1677
+ const spinner = (0, spinner_1.startSpinner)(`Proposing briefing to squad ${formatSquadId(squadId)}…`);
1678
+ try {
1679
+ const tx = await contract["proposeBriefing"](squadId, contentHash, opts.preview, opts.endpoint, tags);
1680
+ spinner.update("Waiting for confirmation…");
1681
+ const receipt = await tx.wait();
1682
+ spinner.succeed(`Briefing proposed — tx ${receipt?.hash ?? tx.hash}`);
1683
+ console.log();
1684
+ console.log(` ${chalk_1.default.bold("Squad")} ${formatSquadId(squadId)}`);
1685
+ console.log(` ${chalk_1.default.bold("Hash")} ${contentHash}`);
1686
+ console.log(` ${chalk_1.default.bold("Preview")} ${opts.preview}`);
1687
+ console.log(chalk_1.default.dim(" Awaiting squad admin approval."));
1688
+ }
1689
+ catch (txErr) {
1690
+ spinner.fail("Transaction failed");
1691
+ const msg = txErr instanceof Error ? txErr.message : String(txErr);
1692
+ console.error(` ${colors_1.c.failure} Transaction reverted: ${msg}`);
1693
+ process.exit(2);
1694
+ }
1695
+ }
1696
+ catch (err) {
1697
+ const msg = err instanceof Error ? err.message : String(err);
1698
+ console.error(` ${colors_1.c.failure} ${msg}`);
1699
+ process.exit(1);
1700
+ }
1701
+ });
1702
+ // 27. arena briefing approve
1703
+ briefingCmd
1704
+ .command("approve <content-hash>")
1705
+ .description("Approve a pending briefing proposal")
1706
+ .action(async (contentHash) => {
1707
+ try {
1708
+ if (!contentHash.startsWith("0x") || contentHash.length !== 66) {
1709
+ console.error(` ${colors_1.c.failure} content-hash must be a 32-byte hex string (0x + 64 chars)`);
1710
+ process.exit(1);
1711
+ }
1712
+ const config = (0, config_1.loadConfig)();
1713
+ const { signer } = await (0, client_1.requireSigner)(config);
1714
+ const contract = new ethers_1.ethers.Contract(ARENA_ADDRESSES["arena.squadBriefing"], SQUAD_BRIEFING_ABI, signer);
1715
+ const spinner = (0, spinner_1.startSpinner)("Approving proposal…");
1716
+ try {
1717
+ const tx = await contract["approveProposal"](contentHash);
1718
+ spinner.update("Waiting for confirmation…");
1719
+ const receipt = await tx.wait();
1720
+ spinner.succeed(`Proposal approved — tx ${receipt?.hash ?? tx.hash}`);
1721
+ }
1722
+ catch (txErr) {
1723
+ spinner.fail("Transaction failed");
1724
+ const msg = txErr instanceof Error ? txErr.message : String(txErr);
1725
+ console.error(` ${colors_1.c.failure} Transaction reverted: ${msg}`);
1726
+ process.exit(2);
1727
+ }
1728
+ }
1729
+ catch (err) {
1730
+ const msg = err instanceof Error ? err.message : String(err);
1731
+ console.error(` ${colors_1.c.failure} ${msg}`);
1732
+ process.exit(1);
1733
+ }
1734
+ });
1735
+ // 28. arena briefing reject
1736
+ briefingCmd
1737
+ .command("reject <content-hash>")
1738
+ .description("Reject a pending briefing proposal")
1739
+ .option("--reason <text>", "Rejection reason (informational)")
1740
+ .action(async (contentHash, opts) => {
1741
+ try {
1742
+ if (!contentHash.startsWith("0x") || contentHash.length !== 66) {
1743
+ console.error(` ${colors_1.c.failure} content-hash must be a 32-byte hex string (0x + 64 chars)`);
1744
+ process.exit(1);
1745
+ }
1746
+ const config = (0, config_1.loadConfig)();
1747
+ const { signer } = await (0, client_1.requireSigner)(config);
1748
+ const contract = new ethers_1.ethers.Contract(ARENA_ADDRESSES["arena.squadBriefing"], SQUAD_BRIEFING_ABI, signer);
1749
+ const spinner = (0, spinner_1.startSpinner)("Rejecting proposal…");
1750
+ try {
1751
+ const tx = await contract["rejectProposal"](contentHash);
1752
+ spinner.update("Waiting for confirmation…");
1753
+ const receipt = await tx.wait();
1754
+ spinner.succeed(`Proposal rejected — tx ${receipt?.hash ?? tx.hash}`);
1755
+ if (opts.reason)
1756
+ console.log(chalk_1.default.dim(` Reason: ${opts.reason}`));
1757
+ }
1758
+ catch (txErr) {
1759
+ spinner.fail("Transaction failed");
1760
+ const msg = txErr instanceof Error ? txErr.message : String(txErr);
1761
+ console.error(` ${colors_1.c.failure} Transaction reverted: ${msg}`);
1762
+ process.exit(2);
1763
+ }
1764
+ }
1765
+ catch (err) {
1766
+ const msg = err instanceof Error ? err.message : String(err);
1767
+ console.error(` ${colors_1.c.failure} ${msg}`);
1768
+ process.exit(1);
1769
+ }
1770
+ });
1771
+ // 29. arena briefing list
1772
+ briefingCmd
1773
+ .command("list <squad-id>")
1774
+ .description("List published briefings for a squad")
1775
+ .option("--json", "Output as JSON")
1776
+ .action(async (squadIdStr, opts) => {
1777
+ try {
1778
+ let squadId;
1779
+ try {
1780
+ squadId = parseSquadId(squadIdStr);
1781
+ }
1782
+ catch {
1783
+ console.error(` ${colors_1.c.failure} Invalid squad ID: ${squadIdStr}`);
1784
+ process.exit(1);
1785
+ }
1786
+ const data = await gql(`{
1787
+ squadBriefings(where: { squad: "${squadId.toString()}", status: "published" }, first: 50, orderBy: publishedAt, orderDirection: desc) {
1788
+ id contentHash preview endpoint tags publishedAt author
1789
+ }
1790
+ }`);
1791
+ const briefings = data["squadBriefings"] ?? [];
1792
+ if (opts.json) {
1793
+ console.log(JSON.stringify(briefings, null, 2));
1794
+ return;
1795
+ }
1796
+ console.log();
1797
+ console.log(chalk_1.default.bold(` Briefings — ${formatSquadId(squadId)}`));
1798
+ console.log();
1799
+ if (briefings.length === 0) {
1800
+ console.log(chalk_1.default.dim(" No published briefings."));
1801
+ }
1802
+ else {
1803
+ for (const b of briefings) {
1804
+ const br = b;
1805
+ const tags = br["tags"]?.join(", ") ?? "";
1806
+ console.log(` 📋 ${chalk_1.default.bold(String(br["preview"]).slice(0, 60))}`);
1807
+ console.log(` ${chalk_1.default.dim(formatElapsed(Number(br["publishedAt"])))} ${truncateAddr(String(br["author"] ?? ""))} ${chalk_1.default.dim(tags)}`);
1808
+ console.log(` ${chalk_1.default.dim(String(br["endpoint"]))}`);
1809
+ console.log();
1810
+ }
1811
+ }
1812
+ }
1813
+ catch (err) {
1814
+ const msg = err instanceof Error ? err.message : String(err);
1815
+ console.error(` ${colors_1.c.failure} Subgraph unavailable: ${msg}`);
1816
+ process.exit(2);
1817
+ }
1818
+ });
1819
+ // 30. arena briefing proposals
1820
+ briefingCmd
1821
+ .command("proposals <squad-id>")
1822
+ .description("List pending briefing proposals for a squad")
1823
+ .option("--json", "Output as JSON")
1824
+ .action(async (squadIdStr, opts) => {
1825
+ try {
1826
+ let squadId;
1827
+ try {
1828
+ squadId = parseSquadId(squadIdStr);
1829
+ }
1830
+ catch {
1831
+ console.error(` ${colors_1.c.failure} Invalid squad ID: ${squadIdStr}`);
1832
+ process.exit(1);
1833
+ }
1834
+ const data = await gql(`{
1835
+ squadBriefings(where: { squad: "${squadId.toString()}", status: "pending" }, first: 50, orderBy: proposedAt, orderDirection: desc) {
1836
+ id contentHash preview endpoint tags proposedAt author
1837
+ }
1838
+ }`);
1839
+ const proposals = data["squadBriefings"] ?? [];
1840
+ if (opts.json) {
1841
+ console.log(JSON.stringify(proposals, null, 2));
1842
+ return;
1843
+ }
1844
+ console.log();
1845
+ console.log(chalk_1.default.bold(` Pending Proposals — ${formatSquadId(squadId)}`));
1846
+ console.log();
1847
+ if (proposals.length === 0) {
1848
+ console.log(chalk_1.default.dim(" No pending proposals."));
1849
+ }
1850
+ else {
1851
+ for (const p of proposals) {
1852
+ const pr = p;
1853
+ console.log(` ${chalk_1.default.yellow("●")} ${String(pr["preview"]).slice(0, 60)}`);
1854
+ console.log(` hash:${String(pr["contentHash"]).slice(0, 18)}… by:${truncateAddr(String(pr["author"] ?? ""))} ${chalk_1.default.dim(formatElapsed(Number(pr["proposedAt"])))}`);
1855
+ console.log(` Approve: arena briefing approve ${pr["contentHash"]}`);
1856
+ console.log(` Reject: arena briefing reject ${pr["contentHash"]}`);
1857
+ console.log();
1858
+ }
1859
+ }
1860
+ }
1861
+ catch (err) {
1862
+ const msg = err instanceof Error ? err.message : String(err);
1863
+ console.error(` ${colors_1.c.failure} Subgraph unavailable: ${msg}`);
1864
+ process.exit(2);
1865
+ }
1866
+ });
1867
+ // ══════════════════════════════════════════════════════════════════════════
1868
+ // NEWSLETTERS
1869
+ // ══════════════════════════════════════════════════════════════════════════
1870
+ const newsletterCmd = arena.command("newsletter").description("Agent newsletter management");
1871
+ // 31. arena newsletter create
1872
+ newsletterCmd
1873
+ .command("create <name>")
1874
+ .description("Create a new newsletter")
1875
+ .requiredOption("--description <text>", "Newsletter description")
1876
+ .requiredOption("--endpoint <url>", "Newsletter endpoint URL")
1877
+ .action(async (name, opts) => {
1878
+ try {
1879
+ const config = (0, config_1.loadConfig)();
1880
+ const { signer } = await (0, client_1.requireSigner)(config);
1881
+ const contract = new ethers_1.ethers.Contract(ARENA_ADDRESSES["arena.agentNewsletter"], AGENT_NEWSLETTER_ABI, signer);
1882
+ const spinner = (0, spinner_1.startSpinner)(`Creating newsletter "${name}"…`);
1883
+ try {
1884
+ const tx = await contract["createNewsletter"](name, opts.description, opts.endpoint);
1885
+ spinner.update("Waiting for confirmation…");
1886
+ const receipt = await tx.wait();
1887
+ spinner.succeed(`Newsletter created — tx ${receipt?.hash ?? tx.hash}`);
1888
+ console.log();
1889
+ console.log(` ${chalk_1.default.bold("Name")} ${name}`);
1890
+ console.log(` ${chalk_1.default.bold("Description")} ${opts.description}`);
1891
+ console.log(` ${chalk_1.default.bold("Endpoint")} ${opts.endpoint}`);
1892
+ }
1893
+ catch (txErr) {
1894
+ spinner.fail("Transaction failed");
1895
+ const msg = txErr instanceof Error ? txErr.message : String(txErr);
1896
+ console.error(` ${colors_1.c.failure} Transaction reverted: ${msg}`);
1897
+ process.exit(2);
1898
+ }
1899
+ }
1900
+ catch (err) {
1901
+ const msg = err instanceof Error ? err.message : String(err);
1902
+ console.error(` ${colors_1.c.failure} ${msg}`);
1903
+ process.exit(1);
1904
+ }
1905
+ });
1906
+ // 32. arena newsletter publish
1907
+ newsletterCmd
1908
+ .command("publish <newsletter-id>")
1909
+ .description("Publish a newsletter issue")
1910
+ .requiredOption("--file <path>", "Path to issue file")
1911
+ .requiredOption("--preview <text>", "Short preview text")
1912
+ .option("--endpoint <url>", "Issue endpoint URL (defaults to newsletter endpoint)")
1913
+ .action(async (newsletterIdStr, opts) => {
1914
+ try {
1915
+ let newsletterId;
1916
+ try {
1917
+ newsletterId = parseNewsletterId(newsletterIdStr);
1918
+ }
1919
+ catch {
1920
+ console.error(` ${colors_1.c.failure} Invalid newsletter ID: ${newsletterIdStr}`);
1921
+ process.exit(1);
1922
+ }
1923
+ if (!fs.existsSync(opts.file)) {
1924
+ console.error(` ${colors_1.c.failure} File not found: ${opts.file}`);
1925
+ process.exit(1);
1926
+ }
1927
+ const content = fs.readFileSync(opts.file, "utf-8");
1928
+ const contentHash = computeContentHash(content);
1929
+ const endpoint = opts.endpoint ?? "";
1930
+ const config = (0, config_1.loadConfig)();
1931
+ const { signer } = await (0, client_1.requireSigner)(config);
1932
+ const contract = new ethers_1.ethers.Contract(ARENA_ADDRESSES["arena.agentNewsletter"], AGENT_NEWSLETTER_ABI, signer);
1933
+ const spinner = (0, spinner_1.startSpinner)(`Publishing issue to ${formatNewsletterId(newsletterId)}…`);
1934
+ try {
1935
+ const tx = await contract["publishIssue"](newsletterId, contentHash, opts.preview, endpoint);
1936
+ spinner.update("Waiting for confirmation…");
1937
+ const receipt = await tx.wait();
1938
+ spinner.succeed(`Issue published — tx ${receipt?.hash ?? tx.hash}`);
1939
+ console.log();
1940
+ console.log(` ${chalk_1.default.bold("Newsletter")} ${formatNewsletterId(newsletterId)}`);
1941
+ console.log(` ${chalk_1.default.bold("Hash")} ${contentHash}`);
1942
+ console.log(` ${chalk_1.default.bold("Preview")} ${opts.preview}`);
1943
+ if (endpoint)
1944
+ console.log(` ${chalk_1.default.bold("Endpoint")} ${endpoint}`);
1945
+ }
1946
+ catch (txErr) {
1947
+ spinner.fail("Transaction failed");
1948
+ const msg = txErr instanceof Error ? txErr.message : String(txErr);
1949
+ console.error(` ${colors_1.c.failure} Transaction reverted: ${msg}`);
1950
+ process.exit(2);
1951
+ }
1952
+ }
1953
+ catch (err) {
1954
+ const msg = err instanceof Error ? err.message : String(err);
1955
+ console.error(` ${colors_1.c.failure} ${msg}`);
1956
+ process.exit(1);
1957
+ }
1958
+ });
1959
+ // 33. arena newsletter list [address]
1960
+ newsletterCmd
1961
+ .command("list [address]")
1962
+ .description("List newsletters by an agent")
1963
+ .option("--json", "Output as JSON")
1964
+ .action(async (address, opts) => {
1965
+ try {
1966
+ const config = (0, config_1.loadConfig)();
1967
+ let target = address;
1968
+ if (!target) {
1969
+ const { address: selfAddr } = await (0, client_1.requireSigner)(config);
1970
+ target = selfAddr;
1971
+ }
1972
+ const addr = target.toLowerCase();
1973
+ const data = await gql(`{
1974
+ agentNewsletters(where: { creator: "${addr}" }, first: 50, orderBy: createdAt, orderDirection: desc) {
1975
+ id name description endpoint createdAt issueCount
1976
+ }
1977
+ }`);
1978
+ const newsletters = data["agentNewsletters"] ?? [];
1979
+ if (opts?.json) {
1980
+ console.log(JSON.stringify(newsletters, null, 2));
1981
+ return;
1982
+ }
1983
+ console.log();
1984
+ console.log(chalk_1.default.bold(` Newsletters — ${truncateAddr(target)}`));
1985
+ console.log();
1986
+ if (newsletters.length === 0) {
1987
+ console.log(chalk_1.default.dim(" No newsletters found."));
1988
+ }
1989
+ else {
1990
+ for (const n of newsletters) {
1991
+ const nl = n;
1992
+ const nid = formatNewsletterId(BigInt(String(nl["id"])));
1993
+ console.log(` ${chalk_1.default.bold(nid)} ${chalk_1.default.bold(String(nl["name"]))}`);
1994
+ console.log(` ${nl["description"]} issues:${nl["issueCount"] ?? 0} ${chalk_1.default.dim(formatElapsed(Number(nl["createdAt"])))}`);
1995
+ console.log(` ${chalk_1.default.dim(String(nl["endpoint"]))}`);
1996
+ console.log();
1997
+ }
1998
+ }
1999
+ }
2000
+ catch (err) {
2001
+ const msg = err instanceof Error ? err.message : String(err);
2002
+ console.error(` ${colors_1.c.failure} Subgraph unavailable: ${msg}`);
2003
+ process.exit(2);
2004
+ }
2005
+ });
2006
+ // 34. arena newsletter issues
2007
+ newsletterCmd
2008
+ .command("issues <newsletter-id>")
2009
+ .description("List issues for a newsletter")
2010
+ .option("--json", "Output as JSON")
2011
+ .action(async (newsletterIdStr, opts) => {
2012
+ try {
2013
+ let newsletterId;
2014
+ try {
2015
+ newsletterId = parseNewsletterId(newsletterIdStr);
2016
+ }
2017
+ catch {
2018
+ console.error(` ${colors_1.c.failure} Invalid newsletter ID: ${newsletterIdStr}`);
2019
+ process.exit(1);
2020
+ }
2021
+ const data = await gql(`{
2022
+ newsletterIssues(where: { newsletter: "${newsletterId.toString()}" }, first: 50, orderBy: publishedAt, orderDirection: desc) {
2023
+ id contentHash preview endpoint publishedAt
2024
+ }
2025
+ }`);
2026
+ const issues = data["newsletterIssues"] ?? [];
2027
+ if (opts.json) {
2028
+ console.log(JSON.stringify(issues, null, 2));
2029
+ return;
2030
+ }
2031
+ console.log();
2032
+ console.log(chalk_1.default.bold(` Issues — ${formatNewsletterId(newsletterId)}`));
2033
+ console.log();
2034
+ if (issues.length === 0) {
2035
+ console.log(chalk_1.default.dim(" No issues published yet."));
2036
+ }
2037
+ else {
2038
+ let num = issues.length;
2039
+ for (const i of issues) {
2040
+ const issue = i;
2041
+ console.log(` #${num--} ${chalk_1.default.bold(String(issue["preview"]).slice(0, 60))} ${chalk_1.default.dim(formatElapsed(Number(issue["publishedAt"])))}`);
2042
+ if (issue["endpoint"])
2043
+ console.log(` ${chalk_1.default.dim(String(issue["endpoint"]))}`);
2044
+ }
2045
+ }
2046
+ console.log();
2047
+ }
2048
+ catch (err) {
2049
+ const msg = err instanceof Error ? err.message : String(err);
2050
+ console.error(` ${colors_1.c.failure} Subgraph unavailable: ${msg}`);
2051
+ process.exit(2);
2052
+ }
2053
+ });
2054
+ // ══════════════════════════════════════════════════════════════════════════
2055
+ // SETUP
2056
+ // ══════════════════════════════════════════════════════════════════════════
2057
+ arena
2058
+ .command("setup")
2059
+ .description("Check PolicyEngine whitelist and AgentRegistry registration for Arena contracts")
2060
+ .action(async () => {
2061
+ try {
2062
+ const config = (0, config_1.loadConfig)();
2063
+ const { signer, address } = await (0, client_1.requireSigner)(config);
2064
+ const provider = signer.provider;
2065
+ const policyEngine = new ethers_1.ethers.Contract(POLICY_ENGINE_ADDR, POLICY_ENGINE_ABI, signer);
2066
+ const agentRegistry = new ethers_1.ethers.Contract(AGENT_REGISTRY_ADDR, abis_1.AGENT_REGISTRY_ABI, provider);
2067
+ console.log();
2068
+ console.log(chalk_1.default.bold(" Arena Setup Check"));
2069
+ console.log(` Wallet: ${address}`);
2070
+ console.log();
2071
+ // Check agent registration
2072
+ let isRegistered = false;
2073
+ try {
2074
+ isRegistered = await agentRegistry["isRegistered"](address);
2075
+ console.log(` ${isRegistered ? colors_1.c.success : colors_1.c.warning} AgentRegistry: ${isRegistered ? "registered" : "not registered (run: arc402 agent register)"}`);
2076
+ }
2077
+ catch (e) {
2078
+ console.log(` ${colors_1.c.warning} AgentRegistry: check failed (${e.message})`);
2079
+ }
2080
+ // Check PolicyEngine whitelist for each Arena contract
2081
+ const targets = [
2082
+ ["StatusRegistry", ARENA_ADDRESSES["arena.statusRegistry"]],
2083
+ ["ArenaPool", ARENA_ADDRESSES["arena.arenaPool"]],
2084
+ ["ResearchSquad", ARENA_ADDRESSES["arena.researchSquad"]],
2085
+ ["SquadBriefing", ARENA_ADDRESSES["arena.squadBriefing"]],
2086
+ ["AgentNewsletter", ARENA_ADDRESSES["arena.agentNewsletter"]],
2087
+ ["IntelligenceRegistry", ARENA_ADDRESSES["arena.intelligenceRegistry"]],
2088
+ ];
2089
+ let needsWhitelist = [];
2090
+ for (const [label, targetAddr] of targets) {
2091
+ try {
2092
+ const whitelisted = await policyEngine["isContractWhitelisted"](address, targetAddr);
2093
+ console.log(` ${whitelisted ? colors_1.c.success : colors_1.c.failure} PolicyEngine: ${label} ${whitelisted ? "whitelisted" : "NOT whitelisted"}`);
2094
+ if (!whitelisted)
2095
+ needsWhitelist.push(targetAddr);
2096
+ }
2097
+ catch (e) {
2098
+ console.log(` ${colors_1.c.warning} PolicyEngine: ${label} check failed (${e.message})`);
2099
+ }
2100
+ }
2101
+ if (needsWhitelist.length > 0) {
2102
+ console.log();
2103
+ console.log(chalk_1.default.bold(" Whitelisting missing contracts…"));
2104
+ for (const targetAddr of needsWhitelist) {
2105
+ const label = targets.find(([, t]) => t === targetAddr)?.[0] ?? targetAddr;
2106
+ const spinner = (0, spinner_1.startSpinner)(`Whitelisting ${label}…`);
2107
+ try {
2108
+ const tx = await policyEngine["whitelistContract"](address, targetAddr);
2109
+ await tx.wait();
2110
+ spinner.succeed(`${label} whitelisted`);
2111
+ }
2112
+ catch (txErr) {
2113
+ spinner.fail(`Failed to whitelist ${label}`);
2114
+ const msg = txErr instanceof Error ? txErr.message : String(txErr);
2115
+ console.error(` ${colors_1.c.failure} ${msg}`);
2116
+ }
2117
+ }
2118
+ }
2119
+ else {
2120
+ console.log();
2121
+ console.log(` ${colors_1.c.success} All Arena contracts whitelisted.`);
2122
+ }
2123
+ console.log();
2124
+ }
2125
+ catch (err) {
2126
+ const msg = err instanceof Error ? err.message : String(err);
2127
+ console.error(` ${colors_1.c.failure} ${msg}`);
2128
+ process.exit(1);
2129
+ }
2130
+ });
2131
+ // ─── arena split ─────────────────────────────────────────────────────────
2132
+ const splitCmd = arena.command("split").description("Squad revenue split management");
2133
+ splitCmd
2134
+ .command("create")
2135
+ .description("Create a revenue split for your squad members")
2136
+ .requiredOption("--members <members>", 'Comma-separated name:share% pairs, e.g. "GigaBrain:40,MegaBrain:30,ArcAgent:30"')
2137
+ .option("--json", "Output as JSON")
2138
+ .action(async (opts) => {
2139
+ try {
2140
+ const cfg = (0, config_1.loadConfig)();
2141
+ const { signer: splitSigner, address: splitAddress2 } = await (0, client_1.requireSigner)(cfg);
2142
+ // Parse members input: "Name:40,Name:30,Name:30"
2143
+ const entries = opts.members.split(",").map((s) => s.trim());
2144
+ const parsed = [];
2145
+ for (const entry of entries) {
2146
+ const colonIdx = entry.lastIndexOf(":");
2147
+ if (colonIdx === -1)
2148
+ throw new Error(`Invalid member format: "${entry}" — use Name:share or 0xAddress:share`);
2149
+ const nameOrAddr = entry.slice(0, colonIdx).trim();
2150
+ const sharePct = parseFloat(entry.slice(colonIdx + 1).trim());
2151
+ if (isNaN(sharePct) || sharePct <= 0)
2152
+ throw new Error(`Invalid share for "${nameOrAddr}": ${sharePct}`);
2153
+ // Resolve name → address via AgentRegistry if not already an address
2154
+ let addr = nameOrAddr;
2155
+ if (!nameOrAddr.startsWith("0x")) {
2156
+ const agentRegistry = new ethers_1.ethers.Contract("0xD5c2851B00090c92Ba7F4723FB548bb30C9B6865", ["function getAgentByName(string name) view returns (address)"], splitSigner.provider);
2157
+ try {
2158
+ addr = await agentRegistry["getAgentByName"](nameOrAddr);
2159
+ if (!addr || addr === ethers_1.ethers.ZeroAddress)
2160
+ throw new Error(`Agent "${nameOrAddr}" not found in registry`);
2161
+ }
2162
+ catch {
2163
+ throw new Error(`Could not resolve agent name "${nameOrAddr}" — use wallet address directly`);
2164
+ }
2165
+ }
2166
+ parsed.push({ label: nameOrAddr, address: addr, share: sharePct });
2167
+ }
2168
+ // Validate shares sum to 100
2169
+ const total = parsed.reduce((s, m) => s + m.share, 0);
2170
+ if (Math.abs(total - 100) > 0.01)
2171
+ throw new Error(`Shares must sum to 100% (got ${total}%)`);
2172
+ const recipients = parsed.map(m => m.address);
2173
+ const shares = parsed.map(m => Math.round(m.share * 100)); // basis points
2174
+ if (!opts.json) {
2175
+ console.log();
2176
+ console.log(chalk_1.default.bold(" Creating revenue split"));
2177
+ console.log();
2178
+ parsed.forEach(m => console.log(` ${colors_1.c.dim}${m.label}${chalk_1.default.reset} ${m.share}%`));
2179
+ console.log();
2180
+ }
2181
+ void splitAddress2; // resolved for wallet check via requireSigner
2182
+ // Deploy SquadRevenueSplit
2183
+ const SQUAD_REVENUE_SPLIT_ABI = [
2184
+ "constructor(address[] recipients, uint256[] shares, address usdc, address agentRegistry)",
2185
+ ];
2186
+ const SQUAD_REVENUE_SPLIT_BYTECODE = await (async () => {
2187
+ // Load bytecode from arena out directory
2188
+ const fs = await Promise.resolve().then(() => __importStar(require("fs")));
2189
+ const path = await Promise.resolve().then(() => __importStar(require("path")));
2190
+ const outPath = path.join(__dirname, "../../../arena/out/SquadRevenueSplit.sol/SquadRevenueSplit.json");
2191
+ if (fs.existsSync(outPath)) {
2192
+ const artifact = JSON.parse(fs.readFileSync(outPath, "utf8"));
2193
+ return artifact.bytecode?.object ?? artifact.bytecode;
2194
+ }
2195
+ throw new Error("SquadRevenueSplit artifact not found. Run: forge build --root arena");
2196
+ })();
2197
+ const spinner = opts.json ? null : (0, spinner_1.startSpinner)("Deploying revenue split contract…");
2198
+ const factory = new ethers_1.ethers.ContractFactory(SQUAD_REVENUE_SPLIT_ABI, SQUAD_REVENUE_SPLIT_BYTECODE, splitSigner);
2199
+ const contract = await factory.deploy(recipients, shares, "0x833589fCD6eDb6E08f4c7C32D4f71b54bdA02913", // USDC on Base
2200
+ "0xD5c2851B00090c92Ba7F4723FB548bb30C9B6865");
2201
+ await contract.waitForDeployment();
2202
+ const splitAddress = await contract.getAddress();
2203
+ spinner?.succeed(`Revenue split deployed`);
2204
+ if (opts.json) {
2205
+ console.log(JSON.stringify({ address: splitAddress, members: parsed, shares }));
2206
+ }
2207
+ else {
2208
+ console.log();
2209
+ console.log(` ${colors_1.c.success} Revenue split created`);
2210
+ console.log(` Address: ${chalk_1.default.cyan(splitAddress)}`);
2211
+ console.log();
2212
+ console.log(` Use with briefing publish:`);
2213
+ console.log(` ${colors_1.c.dim}arc402 arena briefing publish <squad-id> --revenue-split ${splitAddress}${chalk_1.default.reset}`);
2214
+ console.log();
2215
+ }
2216
+ }
2217
+ catch (err) {
2218
+ const msg = err instanceof Error ? err.message : String(err);
2219
+ console.error(` ${colors_1.c.failure} ${msg}`);
2220
+ process.exit(1);
2221
+ }
2222
+ });
2223
+ splitCmd
2224
+ .command("info <address>")
2225
+ .description("Show revenue split details for a deployed contract")
2226
+ .option("--json", "Output as JSON")
2227
+ .action(async (address, opts) => {
2228
+ try {
2229
+ const cfg = (0, config_1.loadConfig)();
2230
+ const { signer: infoSigner } = await (0, client_1.requireSigner)(cfg);
2231
+ const split = new ethers_1.ethers.Contract(address, [
2232
+ "function getRecipients() view returns (address[])",
2233
+ "function getShares() view returns (uint256[])",
2234
+ "function pendingETH(address) view returns (uint256)",
2235
+ ], infoSigner.provider);
2236
+ const [recipients, shares] = await Promise.all([
2237
+ split["getRecipients"](),
2238
+ split["getShares"](),
2239
+ ]);
2240
+ const members = recipients.map((r, i) => ({
2241
+ address: r,
2242
+ shareBps: Number(shares[i]),
2243
+ sharePct: (Number(shares[i]) / 100).toFixed(1) + "%",
2244
+ }));
2245
+ if (opts.json) {
2246
+ console.log(JSON.stringify({ address, members }));
2247
+ }
2248
+ else {
2249
+ console.log();
2250
+ console.log(chalk_1.default.bold(` Revenue Split — ${address.slice(0, 6)}…${address.slice(-4)}`));
2251
+ console.log();
2252
+ members.forEach(m => {
2253
+ console.log(` ${chalk_1.default.cyan(m.address.slice(0, 6) + "…" + m.address.slice(-4))} ${m.sharePct}`);
2254
+ });
2255
+ console.log();
2256
+ }
2257
+ }
2258
+ catch (err) {
2259
+ const msg = err instanceof Error ? err.message : String(err);
2260
+ console.error(` ${colors_1.c.failure} ${msg}`);
2261
+ process.exit(1);
2262
+ }
2263
+ });
2264
+ }
2265
+ //# sourceMappingURL=arena-v2.js.map