arc402-cli 1.4.48 → 1.5.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/commands/arena-v2.d.ts +5 -0
- package/dist/commands/arena-v2.d.ts.map +1 -0
- package/dist/commands/arena-v2.js +2265 -0
- package/dist/commands/arena-v2.js.map +1 -0
- package/dist/commands/arena.d.ts +2 -0
- package/dist/commands/arena.d.ts.map +1 -1
- package/dist/commands/arena.js +10 -28
- package/dist/commands/arena.js.map +1 -1
- package/dist/commands/chat.d.ts +3 -0
- package/dist/commands/chat.d.ts.map +1 -0
- package/dist/commands/chat.js +561 -0
- package/dist/commands/chat.js.map +1 -0
- package/dist/commands/daemon.d.ts.map +1 -1
- package/dist/commands/daemon.js +44 -5
- package/dist/commands/daemon.js.map +1 -1
- package/dist/commands/hermes-init.d.ts +16 -0
- package/dist/commands/hermes-init.d.ts.map +1 -0
- package/dist/commands/hermes-init.js +278 -0
- package/dist/commands/hermes-init.js.map +1 -0
- package/dist/commands/index.d.ts +3 -0
- package/dist/commands/index.d.ts.map +1 -0
- package/dist/commands/index.js +74 -0
- package/dist/commands/index.js.map +1 -0
- package/dist/commands/status.d.ts +18 -0
- package/dist/commands/status.d.ts.map +1 -0
- package/dist/commands/status.js +141 -0
- package/dist/commands/status.js.map +1 -0
- package/dist/commands/subscription.d.ts +3 -0
- package/dist/commands/subscription.d.ts.map +1 -0
- package/dist/commands/subscription.js +173 -0
- package/dist/commands/subscription.js.map +1 -0
- package/dist/commerce-client.d.ts +77 -0
- package/dist/commerce-client.d.ts.map +1 -0
- package/dist/commerce-client.js +224 -0
- package/dist/commerce-client.js.map +1 -0
- package/dist/commerce-index.d.ts +39 -0
- package/dist/commerce-index.d.ts.map +1 -0
- package/dist/commerce-index.js +294 -0
- package/dist/commerce-index.js.map +1 -0
- package/dist/config.d.ts +6 -0
- package/dist/config.d.ts.map +1 -1
- package/dist/config.js.map +1 -1
- package/dist/daemon/config.d.ts +1 -0
- package/dist/daemon/config.d.ts.map +1 -1
- package/dist/daemon/config.js +7 -3
- package/dist/daemon/config.js.map +1 -1
- package/dist/daemon/index.d.ts.map +1 -1
- package/dist/daemon/index.js +102 -5
- package/dist/daemon/index.js.map +1 -1
- package/dist/daemon-client.d.ts +46 -0
- package/dist/daemon-client.d.ts.map +1 -0
- package/dist/daemon-client.js +80 -0
- package/dist/daemon-client.js.map +1 -0
- package/dist/index.js +40 -34
- package/dist/index.js.map +1 -1
- package/dist/program.d.ts.map +1 -1
- package/dist/program.js +10 -0
- package/dist/program.js.map +1 -1
- package/hermes/DELIVERY-SPEC.md +219 -0
- package/hermes/HERMES-INTEGRATION-SPEC.md +338 -0
- package/hermes/plugins/arc402_hermes/__init__.py +5 -0
- package/hermes/plugins/arc402_hermes/plugin.py +489 -0
- package/hermes/plugins/arc402_hermes/py.typed +0 -0
- package/hermes/plugins/arc402_hermes.egg-info/PKG-INFO +24 -0
- package/hermes/plugins/arc402_hermes.egg-info/SOURCES.txt +10 -0
- package/hermes/plugins/arc402_hermes.egg-info/dependency_links.txt +1 -0
- package/hermes/plugins/arc402_hermes.egg-info/entry_points.txt +2 -0
- package/hermes/plugins/arc402_hermes.egg-info/requires.txt +5 -0
- package/hermes/plugins/arc402_hermes.egg-info/top_level.txt +1 -0
- package/hermes/plugins/arc402_plugin.py +489 -0
- package/hermes/plugins/dist/arc402_hermes-1.0.0-py3-none-any.whl +0 -0
- package/hermes/plugins/dist/arc402_hermes-1.0.0.tar.gz +0 -0
- package/hermes/plugins/pyproject.toml +47 -0
- package/hermes/skills/arc402-agent/SKILL.md +559 -0
- package/hermes/workroom/README.md +174 -0
- package/hermes/workroom/hermes-daemon.toml +21 -0
- package/hermes/workroom/hermes-worker/IDENTITY.md +44 -0
- package/hermes/workroom/hermes-worker/SOUL.md +45 -0
- package/hermes/workroom/hermes-worker/config.json +32 -0
- package/hermes/workroom/hermes-worker/datasets/.gitkeep +0 -0
- package/hermes/workroom/hermes-worker/knowledge/.gitkeep +0 -0
- package/hermes/workroom/hermes-worker/memory/learnings.md +9 -0
- package/hermes/workroom/hermes-worker/skills/.gitkeep +0 -0
- package/package.json +9 -3
- package/README.md +0 -288
|
@@ -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
|