@kleros/agentkit 0.1.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/LICENSE +21 -0
- package/README.md +259 -0
- package/dist/cli.d.ts +1 -0
- package/dist/cli.js +5696 -0
- package/dist/index.d.ts +1909 -0
- package/dist/index.js +477 -0
- package/package.json +66 -0
- package/skills/agentkit/SKILL.md +125 -0
package/dist/index.js
ADDED
|
@@ -0,0 +1,477 @@
|
|
|
1
|
+
// src/contracts/abis/v2.ts
|
|
2
|
+
import { getContractsViem } from "@kleros/kleros-v2-contracts/cjs/deployments";
|
|
3
|
+
async function getV2Contracts(publicClient, deployment) {
|
|
4
|
+
return getContractsViem({ publicClient, deployment });
|
|
5
|
+
}
|
|
6
|
+
|
|
7
|
+
// src/contracts/registry.ts
|
|
8
|
+
var V1_CONTRACTS = {
|
|
9
|
+
ethereum: {
|
|
10
|
+
KlerosLiquid: {
|
|
11
|
+
name: "KlerosLiquid",
|
|
12
|
+
proxyAddress: "0x988b3A538b618C7A603e1c11Ab82Cd16dbE28069"
|
|
13
|
+
},
|
|
14
|
+
PolicyRegistry: {
|
|
15
|
+
name: "PolicyRegistry",
|
|
16
|
+
proxyAddress: "0xcf1f07713d5193fae5c1653c9f61953d048bece4"
|
|
17
|
+
},
|
|
18
|
+
PNK: {
|
|
19
|
+
name: "PNK",
|
|
20
|
+
proxyAddress: "0x93ED3FBe21207Ec2E8f2d3c3de6e058Cb73Bc04d"
|
|
21
|
+
},
|
|
22
|
+
KlerosLiquidExtraViews: {
|
|
23
|
+
name: "KlerosLiquidExtraViews",
|
|
24
|
+
proxyAddress: "0x2B562ea613ad2f58746935C842d09EB147E1E940"
|
|
25
|
+
},
|
|
26
|
+
ArbitrableProxy: {
|
|
27
|
+
name: "ArbitrableProxy",
|
|
28
|
+
proxyAddress: "0x99489D7bb33539F3D1A401741E56e8f02B9AE0Cf"
|
|
29
|
+
},
|
|
30
|
+
TransactionBatcher: {
|
|
31
|
+
name: "TransactionBatcher",
|
|
32
|
+
proxyAddress: "0x82458d1C812D7c930Bb3229c9e159cbabD9AA8Cb"
|
|
33
|
+
},
|
|
34
|
+
RNG: {
|
|
35
|
+
name: "RNG",
|
|
36
|
+
proxyAddress: "0x90992fb4E15ce0C59aEFfb376460Fda4Ee19C879"
|
|
37
|
+
}
|
|
38
|
+
},
|
|
39
|
+
gnosis: {
|
|
40
|
+
KlerosLiquid: {
|
|
41
|
+
name: "xKlerosLiquid",
|
|
42
|
+
proxyAddress: "0x9C1dA9A04925bDfDedf0f6421bC7EEa8305F9002",
|
|
43
|
+
implementationAddress: "0x87E1bfEB31Ac4FA857a08471847122ec338F3cF2"
|
|
44
|
+
},
|
|
45
|
+
PolicyRegistry: {
|
|
46
|
+
name: "PolicyRegistry",
|
|
47
|
+
proxyAddress: "0x9d494768936b6bDaabc46733b8D53A937A6c6D7e"
|
|
48
|
+
},
|
|
49
|
+
xPNK: {
|
|
50
|
+
name: "xPNK",
|
|
51
|
+
proxyAddress: "0x37b60f4E9A31A64cCc0024dce7D0fD07eAA0F7B3",
|
|
52
|
+
implementationAddress: "0xf8d1677c8a0c961938bf2f9adc3f3cfda759a9d9"
|
|
53
|
+
},
|
|
54
|
+
stPNK: {
|
|
55
|
+
name: "stPNK",
|
|
56
|
+
proxyAddress: "0xcb3231aBA3b451343e0Fddfc45883c842f223846",
|
|
57
|
+
implementationAddress: "0xad17051DBA7d6992DaB13a0989ecB8E3B2aE519B"
|
|
58
|
+
},
|
|
59
|
+
KlerosLiquidExtraViews: {
|
|
60
|
+
name: "KlerosLiquidExtraViews",
|
|
61
|
+
proxyAddress: "0xFA71f907B48f27d22f670d9E446f8137b0769e4B"
|
|
62
|
+
},
|
|
63
|
+
ArbitrableProxy: {
|
|
64
|
+
name: "ArbitrableProxy",
|
|
65
|
+
proxyAddress: "0xC7aDD3C961f7935CB4914E37DA991D2f1Cd7986c"
|
|
66
|
+
},
|
|
67
|
+
TransactionBatcher: {
|
|
68
|
+
name: "TransactionBatcher",
|
|
69
|
+
proxyAddress: "0x6426800F8508b15AED271337498fa5e7D0794d46"
|
|
70
|
+
},
|
|
71
|
+
RNG: {
|
|
72
|
+
name: "RNG",
|
|
73
|
+
proxyAddress: "0x52a2a2cD6775f35E53ea757Dd2F7E96ee81d7DD7"
|
|
74
|
+
}
|
|
75
|
+
},
|
|
76
|
+
sepolia: {
|
|
77
|
+
KlerosLiquid: {
|
|
78
|
+
name: "KlerosLiquid",
|
|
79
|
+
proxyAddress: "0x90992fb4E15ce0C59aEFfb376460Fda4Ee19C879"
|
|
80
|
+
},
|
|
81
|
+
KlerosLiquidExtraViews: {
|
|
82
|
+
name: "KlerosLiquidExtraViews",
|
|
83
|
+
proxyAddress: "0x5562Ac605764DC4039fb6aB56a74f7321396Cdf2"
|
|
84
|
+
},
|
|
85
|
+
PolicyRegistry: {
|
|
86
|
+
name: "PolicyRegistry",
|
|
87
|
+
proxyAddress: "0x88Fb25D399310c07d35cB9091b8346d8b1893aa5"
|
|
88
|
+
},
|
|
89
|
+
ArbitrableProxy: {
|
|
90
|
+
name: "ArbitrableProxy",
|
|
91
|
+
proxyAddress: "0x009cA5A0B816156F91B29A93d7688c52480BaB24"
|
|
92
|
+
}
|
|
93
|
+
}
|
|
94
|
+
};
|
|
95
|
+
function getV1Contract(chainId, contractName) {
|
|
96
|
+
return V1_CONTRACTS[chainId]?.[contractName];
|
|
97
|
+
}
|
|
98
|
+
|
|
99
|
+
// src/contracts/versions.ts
|
|
100
|
+
function getContractsByChain(config) {
|
|
101
|
+
if (config.version === "v1") {
|
|
102
|
+
return {
|
|
103
|
+
version: "v1",
|
|
104
|
+
getContract: (name) => getV1Contract(config.id, name)
|
|
105
|
+
};
|
|
106
|
+
}
|
|
107
|
+
return {
|
|
108
|
+
version: "v2",
|
|
109
|
+
// RULE-07: config.deployment is threaded through. The cast is safe here — the only
|
|
110
|
+
// caller today (src/infra/onchain.ts fetchDisputeOnChainData) checks config.deployment
|
|
111
|
+
// for undefined and returns DomainErrorCode.DEPLOYMENT_NOT_CONFIGURED BEFORE ever
|
|
112
|
+
// reaching this call site. A future second v2 on-chain caller should either replicate
|
|
113
|
+
// that guard or hoist the check into this function itself.
|
|
114
|
+
getContracts: (publicClient) => getV2Contracts(publicClient, config.deployment)
|
|
115
|
+
};
|
|
116
|
+
}
|
|
117
|
+
|
|
118
|
+
// src/core/types.ts
|
|
119
|
+
function ok(data) {
|
|
120
|
+
return { success: true, data };
|
|
121
|
+
}
|
|
122
|
+
function error(code, message, details = {}) {
|
|
123
|
+
return { success: false, code, message, details };
|
|
124
|
+
}
|
|
125
|
+
|
|
126
|
+
// src/types/errors.ts
|
|
127
|
+
var InfraErrorCode = {
|
|
128
|
+
SUBGRAPH_ERROR: "SUBGRAPH_ERROR",
|
|
129
|
+
IPFS_ERROR: "IPFS_ERROR",
|
|
130
|
+
RPC_ERROR: "RPC_ERROR",
|
|
131
|
+
API_ERROR: "API_ERROR",
|
|
132
|
+
TIMEOUT_ERROR: "TIMEOUT_ERROR",
|
|
133
|
+
BODY_TOO_LARGE: "BODY_TOO_LARGE",
|
|
134
|
+
NETWORK_ERROR: "NETWORK_ERROR",
|
|
135
|
+
WALLET_NOT_CONFIGURED: "WALLET_NOT_CONFIGURED",
|
|
136
|
+
// Phase 4 scaffold: write commands needing a wallet
|
|
137
|
+
IPFS_UNAVAILABLE: "IPFS_UNAVAILABLE"
|
|
138
|
+
// Phase 15 (RELY-02): cdn.kleros.link cannot serve this CID after one attempt — the repin signal. Default-gateway only (see D-28 in src/infra/ipfs.ts). Reserved names AUTH_REQUIRED/QUOTA_EXCEEDED belong to a future SEED-012 — do not reuse them here.
|
|
139
|
+
};
|
|
140
|
+
var DomainErrorCode = {
|
|
141
|
+
DISPUTE_NOT_FOUND: "DISPUTE_NOT_FOUND",
|
|
142
|
+
COURT_NOT_FOUND: "COURT_NOT_FOUND",
|
|
143
|
+
CHAIN_NOT_SUPPORTED: "CHAIN_NOT_SUPPORTED",
|
|
144
|
+
CONFIG_NOT_FOUND: "CONFIG_NOT_FOUND",
|
|
145
|
+
CONTRACT_NOT_FOUND: "CONTRACT_NOT_FOUND",
|
|
146
|
+
// Phase 2 additions (per D-05)
|
|
147
|
+
JUROR_NOT_FOUND: "JUROR_NOT_FOUND",
|
|
148
|
+
EVIDENCE_NOT_FOUND: "EVIDENCE_NOT_FOUND",
|
|
149
|
+
STAKE_NOT_FOUND: "STAKE_NOT_FOUND",
|
|
150
|
+
INVALID_ADDRESS: "INVALID_ADDRESS",
|
|
151
|
+
INVALID_STATUS: "INVALID_STATUS",
|
|
152
|
+
// Phase 4 additions (LIST-01..04)
|
|
153
|
+
INVALID_LIMIT: "INVALID_LIMIT",
|
|
154
|
+
INVALID_ORDER_BY: "INVALID_ORDER_BY",
|
|
155
|
+
INVALID_ORDER_DIRECTION: "INVALID_ORDER_DIRECTION",
|
|
156
|
+
INVALID_CURSOR: "INVALID_CURSOR",
|
|
157
|
+
// Phase 6 additions
|
|
158
|
+
CHAIN_REQUIRED_FOR_COURT_FILTER: "CHAIN_REQUIRED_FOR_COURT_FILTER",
|
|
159
|
+
// --court requires --chain (D-20)
|
|
160
|
+
CHAIN_REQUIRED: "CHAIN_REQUIRED",
|
|
161
|
+
// command requires explicit --chain (e.g. juror info)
|
|
162
|
+
INVALID_COURT_ID: "INVALID_COURT_ID",
|
|
163
|
+
// --court value is not a non-negative integer (juror top)
|
|
164
|
+
// Phase 7 additions
|
|
165
|
+
INVALID_CONFIG_KEY: "INVALID_CONFIG_KEY",
|
|
166
|
+
// config set/unset received an unrecognised key
|
|
167
|
+
// Phase 9 additions (Meta-Evidence Resolver + Reality Handler)
|
|
168
|
+
// Resolver-layer hard failures — surfaced as success: false to callers:
|
|
169
|
+
META_EVIDENCE_NOT_FOUND: "META_EVIDENCE_NOT_FOUND",
|
|
170
|
+
META_EVIDENCE_PARSE_ERROR: "META_EVIDENCE_PARSE_ERROR",
|
|
171
|
+
// Handler-layer soft failures — caught by dispatcher, surfaced as enrichment.handler_warnings[]:
|
|
172
|
+
// These are returned from handler as error() but NEVER propagate as top-level success: false
|
|
173
|
+
// (D-10/D-11: KlerosResult<MetaEvidence> is success: true whenever MetaEvidence was fetched+parsed)
|
|
174
|
+
REALITY_DISPUTE_QUESTION_NOT_FOUND: "REALITY_DISPUTE_QUESTION_NOT_FOUND",
|
|
175
|
+
REALITY_QUESTION_NOT_FOUND: "REALITY_QUESTION_NOT_FOUND",
|
|
176
|
+
REALITY_TEMPLATE_NOT_FOUND: "REALITY_TEMPLATE_NOT_FOUND",
|
|
177
|
+
REALITY_HANDLER_FAILURE: "REALITY_HANDLER_FAILURE",
|
|
178
|
+
// Phase 12 additions (Composite Brief + Feedback Loop)
|
|
179
|
+
RATE_LIMITED: "RATE_LIMITED",
|
|
180
|
+
// report-issue: 3/60s sliding window exceeded (FB-04)
|
|
181
|
+
GITHUB_TOKEN_MISSING: "GITHUB_TOKEN_MISSING",
|
|
182
|
+
// --submit but GITHUB_TOKEN env var absent (D-09)
|
|
183
|
+
REPORT_WRITE_FAILED: "REPORT_WRITE_FAILED",
|
|
184
|
+
// local file write to ~/.kleros/reports/ failed
|
|
185
|
+
// Phase 14 additions (Ruling Semantics + URI Cleanup + Deployment Threading)
|
|
186
|
+
DISPUTE_ENRICHMENT_FAILED: "DISPUTE_ENRICHMENT_FAILED",
|
|
187
|
+
// on-chain ruling read failed on an existing dispute
|
|
188
|
+
DEPLOYMENT_NOT_CONFIGURED: "DEPLOYMENT_NOT_CONFIGURED"
|
|
189
|
+
// v2 ChainConfig missing `deployment`
|
|
190
|
+
};
|
|
191
|
+
|
|
192
|
+
// src/infra/http.ts
|
|
193
|
+
var DEFAULT_TIMEOUT = 1e4;
|
|
194
|
+
async function fetchJson(url, options) {
|
|
195
|
+
const timeout = options?.timeout ?? DEFAULT_TIMEOUT;
|
|
196
|
+
try {
|
|
197
|
+
const response = await fetch(url, {
|
|
198
|
+
signal: AbortSignal.timeout(timeout)
|
|
199
|
+
});
|
|
200
|
+
if (!response.ok) {
|
|
201
|
+
return error(InfraErrorCode.API_ERROR, `HTTP ${response.status} ${response.statusText}`, {
|
|
202
|
+
url,
|
|
203
|
+
status: response.status
|
|
204
|
+
});
|
|
205
|
+
}
|
|
206
|
+
const data = await response.json();
|
|
207
|
+
return ok(data);
|
|
208
|
+
} catch (e) {
|
|
209
|
+
if (e instanceof DOMException && (e.name === "TimeoutError" || e.name === "AbortError")) {
|
|
210
|
+
return error(InfraErrorCode.TIMEOUT_ERROR, "HTTP fetch timed out", { url, timeout });
|
|
211
|
+
}
|
|
212
|
+
const message = e instanceof Error ? e.message : String(e);
|
|
213
|
+
return error(InfraErrorCode.API_ERROR, message, { url });
|
|
214
|
+
}
|
|
215
|
+
}
|
|
216
|
+
|
|
217
|
+
// src/infra/ipfs.ts
|
|
218
|
+
import QuickLRU from "quick-lru";
|
|
219
|
+
|
|
220
|
+
// src/infra/codec.ts
|
|
221
|
+
function decodeAsJson(bytes) {
|
|
222
|
+
try {
|
|
223
|
+
const text = new TextDecoder().decode(bytes);
|
|
224
|
+
return JSON.parse(text);
|
|
225
|
+
} catch {
|
|
226
|
+
return null;
|
|
227
|
+
}
|
|
228
|
+
}
|
|
229
|
+
function decodeAsText(bytes) {
|
|
230
|
+
return new TextDecoder().decode(bytes);
|
|
231
|
+
}
|
|
232
|
+
|
|
233
|
+
// src/infra/ipfs.ts
|
|
234
|
+
var DEFAULT_GATEWAY = "https://cdn.kleros.link";
|
|
235
|
+
var DEFAULT_TIMEOUT2 = 1e4;
|
|
236
|
+
var MAX_CACHED_BODY_BYTES = 1048576;
|
|
237
|
+
var _ipfsCache = new QuickLRU({
|
|
238
|
+
maxSize: 200
|
|
239
|
+
});
|
|
240
|
+
var _inflight = /* @__PURE__ */ new Map();
|
|
241
|
+
function normalizeIpfsCid(uri) {
|
|
242
|
+
if (uri.startsWith("ipfs://")) return uri.slice(7);
|
|
243
|
+
if (uri.startsWith("/ipfs/")) return uri.slice(6);
|
|
244
|
+
return uri;
|
|
245
|
+
}
|
|
246
|
+
async function _fetchRaw(url, timeout, uri, isDefaultGateway, maxBodyBytes) {
|
|
247
|
+
try {
|
|
248
|
+
const response = await fetch(url, { signal: AbortSignal.timeout(timeout) });
|
|
249
|
+
if (!response.ok) {
|
|
250
|
+
if (isDefaultGateway && response.status >= 500) {
|
|
251
|
+
const cid = normalizeIpfsCid(uri);
|
|
252
|
+
return error(
|
|
253
|
+
InfraErrorCode.IPFS_UNAVAILABLE,
|
|
254
|
+
`IPFS_UNAVAILABLE: cdn.kleros.link returned ${response.status} ${response.statusText} for this CID \u2014 content may be unpinned. No automatic gateway fallback is attempted (by design).`,
|
|
255
|
+
{ uri, cid, status: response.status, reason: "http-5xx" }
|
|
256
|
+
);
|
|
257
|
+
}
|
|
258
|
+
return error(
|
|
259
|
+
InfraErrorCode.IPFS_ERROR,
|
|
260
|
+
`IPFS fetch failed: ${response.status} ${response.statusText}`,
|
|
261
|
+
{ uri, status: response.status }
|
|
262
|
+
);
|
|
263
|
+
}
|
|
264
|
+
if (maxBodyBytes !== void 0) {
|
|
265
|
+
const cl = response.headers.get("content-length");
|
|
266
|
+
if (cl !== null) {
|
|
267
|
+
const clNum = Number(cl);
|
|
268
|
+
if (!Number.isNaN(clNum) && clNum > maxBodyBytes) {
|
|
269
|
+
return error(
|
|
270
|
+
InfraErrorCode.BODY_TOO_LARGE,
|
|
271
|
+
`IPFS body too large: Content-Length ${clNum} exceeds ${maxBodyBytes}`,
|
|
272
|
+
{ uri, contentLength: clNum, maxBodyBytes }
|
|
273
|
+
);
|
|
274
|
+
}
|
|
275
|
+
}
|
|
276
|
+
}
|
|
277
|
+
const contentType = response.headers.get("content-type");
|
|
278
|
+
const arrayBuffer = await response.arrayBuffer();
|
|
279
|
+
const body = new Uint8Array(arrayBuffer);
|
|
280
|
+
return ok({ body, contentType });
|
|
281
|
+
} catch (e) {
|
|
282
|
+
if (e instanceof DOMException && (e.name === "TimeoutError" || e.name === "AbortError")) {
|
|
283
|
+
if (isDefaultGateway) {
|
|
284
|
+
const cid = normalizeIpfsCid(uri);
|
|
285
|
+
return error(
|
|
286
|
+
InfraErrorCode.IPFS_UNAVAILABLE,
|
|
287
|
+
"IPFS_UNAVAILABLE: cdn.kleros.link timed out fetching this CID \u2014 content may be unpinned. No automatic gateway fallback is attempted (by design).",
|
|
288
|
+
{ uri, cid, timeout, reason: "timeout" }
|
|
289
|
+
);
|
|
290
|
+
}
|
|
291
|
+
return error(InfraErrorCode.TIMEOUT_ERROR, "IPFS fetch timed out", { uri, timeout });
|
|
292
|
+
}
|
|
293
|
+
const message = e instanceof Error ? e.message : String(e);
|
|
294
|
+
return error(InfraErrorCode.IPFS_ERROR, message, { uri });
|
|
295
|
+
}
|
|
296
|
+
}
|
|
297
|
+
function _fetchRawCoalesced(url, timeout, uri, isDefaultGateway, maxBodyBytes, cid, gateway) {
|
|
298
|
+
const key = `${cid}|${maxBodyBytes ?? "none"}|${gateway}`;
|
|
299
|
+
const existing = _inflight.get(key);
|
|
300
|
+
if (existing !== void 0) return existing;
|
|
301
|
+
const promise = _fetchRaw(url, timeout, uri, isDefaultGateway, maxBodyBytes).finally(() => {
|
|
302
|
+
_inflight.delete(key);
|
|
303
|
+
});
|
|
304
|
+
_inflight.set(key, promise);
|
|
305
|
+
return promise;
|
|
306
|
+
}
|
|
307
|
+
async function fetchIpfs(uri, options) {
|
|
308
|
+
const gateway = options?.gateway ?? DEFAULT_GATEWAY;
|
|
309
|
+
const timeout = options?.timeout ?? DEFAULT_TIMEOUT2;
|
|
310
|
+
const cid = normalizeIpfsCid(uri);
|
|
311
|
+
const cacheKey = `${gateway}|${cid}`;
|
|
312
|
+
const cached = _ipfsCache.get(cacheKey);
|
|
313
|
+
if (cached !== void 0) {
|
|
314
|
+
const json2 = decodeAsJson(cached.body);
|
|
315
|
+
if (json2 !== null) return ok(json2);
|
|
316
|
+
return ok(decodeAsText(cached.body));
|
|
317
|
+
}
|
|
318
|
+
const url = `${gateway}/ipfs/${cid}`;
|
|
319
|
+
const isDefaultGateway = gateway === DEFAULT_GATEWAY;
|
|
320
|
+
const raw = await _fetchRawCoalesced(
|
|
321
|
+
url,
|
|
322
|
+
timeout,
|
|
323
|
+
uri,
|
|
324
|
+
isDefaultGateway,
|
|
325
|
+
void 0,
|
|
326
|
+
cid,
|
|
327
|
+
gateway
|
|
328
|
+
);
|
|
329
|
+
if (!raw.success) return raw;
|
|
330
|
+
if (raw.data.body.length <= MAX_CACHED_BODY_BYTES) {
|
|
331
|
+
_ipfsCache.set(cacheKey, raw.data);
|
|
332
|
+
}
|
|
333
|
+
const json = decodeAsJson(raw.data.body);
|
|
334
|
+
if (json !== null) return ok(json);
|
|
335
|
+
return ok(decodeAsText(raw.data.body));
|
|
336
|
+
}
|
|
337
|
+
|
|
338
|
+
// src/infra/ipfs-walk.ts
|
|
339
|
+
import { fileTypeFromBuffer } from "file-type";
|
|
340
|
+
|
|
341
|
+
// src/infra/rpc.ts
|
|
342
|
+
import { createPublicClient, http } from "viem";
|
|
343
|
+
|
|
344
|
+
// src/types/network.ts
|
|
345
|
+
function getSubgraphUrl(chain, type) {
|
|
346
|
+
const envKey = `KLEROS_SUBGRAPH_URL_${chain.id.toUpperCase().replace("-", "_")}_${type.toUpperCase()}`;
|
|
347
|
+
const envValue = process.env[envKey];
|
|
348
|
+
if (envValue) return envValue;
|
|
349
|
+
return type === "core" ? chain.subgraph.core : chain.subgraph.drt;
|
|
350
|
+
}
|
|
351
|
+
function getRpcUrl(chain) {
|
|
352
|
+
const envKey = `KLEROS_RPC_URL_${chain.id.toUpperCase().replace("-", "_")}`;
|
|
353
|
+
const envValue = process.env[envKey];
|
|
354
|
+
if (envValue) return envValue;
|
|
355
|
+
return chain.rpcUrl;
|
|
356
|
+
}
|
|
357
|
+
|
|
358
|
+
// src/infra/rpc.ts
|
|
359
|
+
function createKlerosClient(config) {
|
|
360
|
+
return createPublicClient({
|
|
361
|
+
chain: config.viemChain,
|
|
362
|
+
transport: http(getRpcUrl(config))
|
|
363
|
+
});
|
|
364
|
+
}
|
|
365
|
+
|
|
366
|
+
// src/infra/subgraph.ts
|
|
367
|
+
import { GraphQLClient } from "graphql-request";
|
|
368
|
+
async function querySubgraph(url, query, variables) {
|
|
369
|
+
try {
|
|
370
|
+
const client = new GraphQLClient(url);
|
|
371
|
+
const data = await client.request(query, variables);
|
|
372
|
+
return ok(data);
|
|
373
|
+
} catch (e) {
|
|
374
|
+
const message = e instanceof Error ? e.message : String(e);
|
|
375
|
+
return error(InfraErrorCode.SUBGRAPH_ERROR, message, { url, query });
|
|
376
|
+
}
|
|
377
|
+
}
|
|
378
|
+
async function querySubgraphPaginated(url, query, entityName, variables, pageSize = 100) {
|
|
379
|
+
const client = new GraphQLClient(url);
|
|
380
|
+
const allEntities = [];
|
|
381
|
+
let lastId = "";
|
|
382
|
+
try {
|
|
383
|
+
while (true) {
|
|
384
|
+
const vars = { ...variables, first: pageSize, lastId };
|
|
385
|
+
const data = await client.request(query, vars);
|
|
386
|
+
const page = data[entityName];
|
|
387
|
+
if (!page || page.length === 0) {
|
|
388
|
+
break;
|
|
389
|
+
}
|
|
390
|
+
allEntities.push(...page);
|
|
391
|
+
if (page.length < pageSize) {
|
|
392
|
+
break;
|
|
393
|
+
}
|
|
394
|
+
lastId = page[page.length - 1].id;
|
|
395
|
+
}
|
|
396
|
+
return ok(allEntities);
|
|
397
|
+
} catch (e) {
|
|
398
|
+
const message = e instanceof Error ? e.message : String(e);
|
|
399
|
+
return error(InfraErrorCode.SUBGRAPH_ERROR, message, { url, entityName });
|
|
400
|
+
}
|
|
401
|
+
}
|
|
402
|
+
|
|
403
|
+
// src/types/chains.ts
|
|
404
|
+
import { arbitrumSepolia, gnosis, mainnet, sepolia } from "viem/chains";
|
|
405
|
+
var CHAIN_CONFIGS = {
|
|
406
|
+
ethereum: {
|
|
407
|
+
id: "ethereum",
|
|
408
|
+
chainId: 1,
|
|
409
|
+
viemChain: mainnet,
|
|
410
|
+
version: "v1",
|
|
411
|
+
name: "Ethereum",
|
|
412
|
+
rpcUrl: "https://eth.llamarpc.com",
|
|
413
|
+
subgraph: {
|
|
414
|
+
core: "https://gateway.thegraph.com/api/28aec30509f7a09a74a0690880025e3b/subgraphs/id/BqbBhB4R5pNAtdYya2kcojMrQMp8nVHioUnP22qN8JoN"
|
|
415
|
+
}
|
|
416
|
+
},
|
|
417
|
+
gnosis: {
|
|
418
|
+
id: "gnosis",
|
|
419
|
+
chainId: 100,
|
|
420
|
+
viemChain: gnosis,
|
|
421
|
+
version: "v1",
|
|
422
|
+
name: "Gnosis",
|
|
423
|
+
rpcUrl: "https://rpc.gnosischain.com",
|
|
424
|
+
subgraph: {
|
|
425
|
+
core: "https://gateway.thegraph.com/api/28aec30509f7a09a74a0690880025e3b/subgraphs/id/FxhLntVBELrZ4t1c2HNNvLWEYfBjpB8iKZiEymuFSPSr"
|
|
426
|
+
}
|
|
427
|
+
},
|
|
428
|
+
"arbitrum-sepolia": {
|
|
429
|
+
id: "arbitrum-sepolia",
|
|
430
|
+
chainId: 421614,
|
|
431
|
+
viemChain: arbitrumSepolia,
|
|
432
|
+
version: "v2",
|
|
433
|
+
name: "Arbitrum Sepolia (devnet)",
|
|
434
|
+
rpcUrl: "https://sepolia-rollup.arbitrum.io/rpc",
|
|
435
|
+
subgraph: {
|
|
436
|
+
core: "https://gateway.thegraph.com/api/28aec30509f7a09a74a0690880025e3b/subgraphs/id/4Rg3NAW99hX8RDD281ycfeRWxp92k6RdqpW3pf6imGTS",
|
|
437
|
+
drt: "https://gateway.thegraph.com/api/28aec30509f7a09a74a0690880025e3b/subgraphs/id/HZHLr6QXebWvttsCuJsSEzYGuQLHZNdVLfEcGUSESoMs"
|
|
438
|
+
},
|
|
439
|
+
deployment: "devnet"
|
|
440
|
+
},
|
|
441
|
+
sepolia: {
|
|
442
|
+
id: "sepolia",
|
|
443
|
+
chainId: 11155111,
|
|
444
|
+
viemChain: sepolia,
|
|
445
|
+
version: "v1",
|
|
446
|
+
name: "Sepolia (testnet)",
|
|
447
|
+
rpcUrl: "https://ethereum-sepolia-rpc.publicnode.com",
|
|
448
|
+
// DEFAULT — override via KLEROS_RPC_URL_SEPOLIA (Infura/Alchemy required for event scans)
|
|
449
|
+
subgraph: {
|
|
450
|
+
core: "https://gateway.thegraph.com/api/28aec30509f7a09a74a0690880025e3b/subgraphs/id/DCc7YMYrpTodkQpmmyduPmtbaQNkNtx5UMm1RsuVX7vn"
|
|
451
|
+
}
|
|
452
|
+
}
|
|
453
|
+
};
|
|
454
|
+
var DEFAULT_CHAIN = "ethereum";
|
|
455
|
+
function resolveChainConfig(id) {
|
|
456
|
+
return CHAIN_CONFIGS[id];
|
|
457
|
+
}
|
|
458
|
+
export {
|
|
459
|
+
CHAIN_CONFIGS,
|
|
460
|
+
DEFAULT_CHAIN,
|
|
461
|
+
DomainErrorCode,
|
|
462
|
+
InfraErrorCode,
|
|
463
|
+
V1_CONTRACTS,
|
|
464
|
+
createKlerosClient,
|
|
465
|
+
error,
|
|
466
|
+
fetchIpfs,
|
|
467
|
+
fetchJson,
|
|
468
|
+
getContractsByChain,
|
|
469
|
+
getRpcUrl,
|
|
470
|
+
getSubgraphUrl,
|
|
471
|
+
getV1Contract,
|
|
472
|
+
getV2Contracts,
|
|
473
|
+
ok,
|
|
474
|
+
querySubgraph,
|
|
475
|
+
querySubgraphPaginated,
|
|
476
|
+
resolveChainConfig
|
|
477
|
+
};
|
package/package.json
ADDED
|
@@ -0,0 +1,66 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "@kleros/agentkit",
|
|
3
|
+
"version": "0.1.0",
|
|
4
|
+
"type": "module",
|
|
5
|
+
"description": "Agent-first CLI/MCP/HTTP toolkit for Kleros dispute resolution",
|
|
6
|
+
"repository": {
|
|
7
|
+
"type": "git",
|
|
8
|
+
"url": "git+https://github.com/kleros/agentkit.git"
|
|
9
|
+
},
|
|
10
|
+
"main": "dist/index.js",
|
|
11
|
+
"types": "dist/index.d.ts",
|
|
12
|
+
"exports": {
|
|
13
|
+
".": {
|
|
14
|
+
"types": "./dist/index.d.ts",
|
|
15
|
+
"import": "./dist/index.js"
|
|
16
|
+
}
|
|
17
|
+
},
|
|
18
|
+
"bin": {
|
|
19
|
+
"kleros": "dist/cli.js"
|
|
20
|
+
},
|
|
21
|
+
"files": [
|
|
22
|
+
"dist",
|
|
23
|
+
"skills/agentkit/SKILL.md",
|
|
24
|
+
"README.md",
|
|
25
|
+
"LICENSE"
|
|
26
|
+
],
|
|
27
|
+
"publishConfig": {
|
|
28
|
+
"access": "public"
|
|
29
|
+
},
|
|
30
|
+
"scripts": {
|
|
31
|
+
"dev": "tsx src/cli.ts",
|
|
32
|
+
"build": "tsup",
|
|
33
|
+
"test": "vitest",
|
|
34
|
+
"test:integration": "vitest --config vitest.integration.config.ts",
|
|
35
|
+
"lint": "biome check .",
|
|
36
|
+
"format": "biome format . --write",
|
|
37
|
+
"lint:no-eval": "! grep -rE 'eval\\(|new Function\\(|vm\\.runInNewContext\\(' src/contracts/ src/core/",
|
|
38
|
+
"verify:skill": "bash scripts/verify-skill-commands.sh",
|
|
39
|
+
"update-deployment-blocks": "tsx scripts/update-deployment-blocks.ts",
|
|
40
|
+
"prepublishOnly": "pnpm build"
|
|
41
|
+
},
|
|
42
|
+
"dependencies": {
|
|
43
|
+
"@kleros/kleros-v2-contracts": "^2.0.0-rc.2",
|
|
44
|
+
"conf": "^15.1.0",
|
|
45
|
+
"dotenv": "^17.3.1",
|
|
46
|
+
"file-type": "~21.3.4",
|
|
47
|
+
"graphql": "^16.10.0",
|
|
48
|
+
"graphql-request": "^7.4.0",
|
|
49
|
+
"incur": "~0.4.11",
|
|
50
|
+
"picocolors": "^1.1.1",
|
|
51
|
+
"quick-lru": "^7.3.0",
|
|
52
|
+
"viem": "^2.47.6"
|
|
53
|
+
},
|
|
54
|
+
"devDependencies": {
|
|
55
|
+
"@biomejs/biome": "^2.4.8",
|
|
56
|
+
"@types/node": "^25.5.0",
|
|
57
|
+
"tsup": "^8.5.1",
|
|
58
|
+
"tsx": "^4.21.0",
|
|
59
|
+
"typescript": "~5.8.3",
|
|
60
|
+
"vitest": "^4.1.1"
|
|
61
|
+
},
|
|
62
|
+
"engines": {
|
|
63
|
+
"node": ">=22"
|
|
64
|
+
},
|
|
65
|
+
"license": "MIT"
|
|
66
|
+
}
|
|
@@ -0,0 +1,125 @@
|
|
|
1
|
+
---
|
|
2
|
+
name: agentkit
|
|
3
|
+
description: Query the Kleros dispute resolution protocol — disputes, courts, jurors, evidence, policy, arbitrable contracts — and compose peer MCPs (markitdown) for binary content.
|
|
4
|
+
trigger: when the user asks about Kleros disputes, court policy, juror behavior, PNK staking, dispute evidence, or arbitrable contract classification
|
|
5
|
+
---
|
|
6
|
+
|
|
7
|
+
## What this skill does
|
|
8
|
+
|
|
9
|
+
AgentKit (`@kleros/agentkit`) is a token-efficient CLI/MCP/HTTP toolkit for the Kleros dispute resolution protocol. It ships three command groups: v1.0 (courts, disputes, evidence, jurors, stakes, config — Ethereum and Gnosis), v1.1 (juror leaderboard — cross-chain), and v1.2 (dispute policy, composite brief, arbitrable classification, issue reporting — v1 chains only). All output defaults to TOON format (40% fewer tokens); use `--format json`, `--format yaml`, or `--format md` when structured parsing is needed.
|
|
10
|
+
|
|
11
|
+
## Command surface
|
|
12
|
+
|
|
13
|
+
| Command | What it does | Chains | Version |
|
|
14
|
+
|---------|-------------|--------|---------|
|
|
15
|
+
| `kleros arbitrable classify <address>` | Classify an arbitrable contract as a known Kleros type (curated registry hit) or free-text category (Meta-Evidence fallback — callers must not branch on category value). Requires `--chain`. | ethereum, gnosis | v1.2 |
|
|
16
|
+
| `kleros config get <key>` | Read a single persisted configuration value by key. | any | v1.0 |
|
|
17
|
+
| `kleros config list` | List all persisted configuration values. | any | v1.0 |
|
|
18
|
+
| `kleros config set <key> <value>` | Persist a configuration default (e.g., chain) so subsequent commands inherit it without `--chain`. | any | v1.0 |
|
|
19
|
+
| `kleros config unset <key>` | Remove a persisted configuration key. | any | v1.0 |
|
|
20
|
+
| `kleros court get <id>` | Fetch detailed parameters for a specific court: timing, fee, min stake, parent, hidden votes. | ethereum, gnosis | v1.0 |
|
|
21
|
+
| `kleros court list` | List all Kleros arbitration courts with stake requirements and fee structure. | ethereum, gnosis | v1.0 |
|
|
22
|
+
| `kleros court policy <id>` | Retrieve the arbitration policy document for a court from IPFS. | ethereum, gnosis | v1.0 |
|
|
23
|
+
| `kleros dispute brief <id>` | Composite snapshot: policy URIs, evidence with IPFS hints, ruling labels, parties, appeal state. `--depth 0` subgraph only; `--depth 1` (default) resolved URIs; `--depth 2` full IPFS content with extractionHints. **v1 only** — arbitrum-sepolia returns `CHAIN_NOT_SUPPORTED`. | ethereum, gnosis | v1.2 |
|
|
24
|
+
| `kleros dispute get <id>` | Fetch full dispute details: period, juror count, ruling, deadlines, arbitrated contract. | ethereum, gnosis, arbitrum-sepolia | v1.0 |
|
|
25
|
+
| `kleros dispute list` | List disputes with optional filters (court, status, chain). | ethereum, gnosis, arbitrum-sepolia | v1.0 |
|
|
26
|
+
| `kleros dispute policy <id>` | Fetch dispute-specific policy via Meta-Evidence chain. Returns `metaEvidenceUri`, `policyUri`, and enrichment. Effective constraint = court ∩ dispute — always also fetch court policy. **v1 only** — arbitrum-sepolia returns `CHAIN_NOT_SUPPORTED`. | ethereum, gnosis | v1.2 |
|
|
27
|
+
| `kleros evidence list` | List evidence submitted for a dispute including resolved IPFS metadata. Items with binary attachments carry `extractionHint` for markitdown-mcp composition. | ethereum, gnosis, arbitrum-sepolia | v1.0/v1.2 |
|
|
28
|
+
| `kleros juror info` | Fetch a juror's profile: PNK balance breakdown, active dispute count, coherence score, per-court stake positions. | ethereum, gnosis | v1.0 |
|
|
29
|
+
| `kleros juror top` | Rank jurors by total staked PNK. Without `--chain`, merges ethereum + gnosis. | ethereum, gnosis | v1.1 |
|
|
30
|
+
| `kleros report-issue create` | Create a feedback report. Writes a local `.md` file by default; `--submit` posts to GitHub Issues (requires `GITHUB_TOKEN`). | any | v1.2 |
|
|
31
|
+
| `kleros stake list` | List PNK token stakes held by a juror address across courts. | ethereum, gnosis | v1.0 |
|
|
32
|
+
|
|
33
|
+
Run `kleros --llms-full` for the full manifest. Run `kleros <command> --schema` for argument details.
|
|
34
|
+
|
|
35
|
+
## Common workflows
|
|
36
|
+
|
|
37
|
+
### Example 1 — Policy resolution (intersection rule)
|
|
38
|
+
|
|
39
|
+
```
|
|
40
|
+
Q: What does Kleros consider when ruling on dispute 797?
|
|
41
|
+
|
|
42
|
+
A: Run two commands together — the effective juror constraint is the intersection:
|
|
43
|
+
|
|
44
|
+
kleros court policy <courtId> --chain gnosis
|
|
45
|
+
kleros dispute policy 797 --chain gnosis
|
|
46
|
+
|
|
47
|
+
court policy = what the court always requires
|
|
48
|
+
dispute policy = what this dispute adds
|
|
49
|
+
|
|
50
|
+
The effective juror constraint = court ∩ dispute. The juror must satisfy BOTH.
|
|
51
|
+
Neither alone is sufficient.
|
|
52
|
+
```
|
|
53
|
+
|
|
54
|
+
### Example 2 — Evidence with binary attachments
|
|
55
|
+
|
|
56
|
+
```
|
|
57
|
+
Q: What evidence was submitted, including the attached PDFs?
|
|
58
|
+
|
|
59
|
+
A: Step 1: kleros evidence list --dispute 797 --chain gnosis
|
|
60
|
+
Step 2: For each item with an extractionHint field:
|
|
61
|
+
Call peer MCP: convert_to_markdown(uri=<extractionHint.uri>)
|
|
62
|
+
AgentKit returns URIs and extractionHints — markitdown-mcp does the text extraction.
|
|
63
|
+
AgentKit never extracts the binary itself.
|
|
64
|
+
```
|
|
65
|
+
|
|
66
|
+
### Example 3 — Composite brief
|
|
67
|
+
|
|
68
|
+
```
|
|
69
|
+
Q: Give me everything about dispute 797 in one call.
|
|
70
|
+
|
|
71
|
+
A: kleros dispute brief 797 --chain gnosis --depth 1
|
|
72
|
+
|
|
73
|
+
Returns: policy URIs, evidence list with extractionHints, ruling labels, parties, appeal state.
|
|
74
|
+
For full IPFS-resolved content: --depth 2 (higher token cost).
|
|
75
|
+
Note: roundHistoryNote will explain round history is deferred to v1.3.
|
|
76
|
+
```
|
|
77
|
+
|
|
78
|
+
## Composing peer MCPs — markitdown-mcp
|
|
79
|
+
|
|
80
|
+
When `evidence list` or `dispute brief` returns an item with an `extractionHint`, the agent should call the peer MCP server `markitdown-mcp` to extract text content from the binary attachment. AgentKit never bundles or calls markitdown directly — it returns URIs and lets agents compose the peer MCP.
|
|
81
|
+
|
|
82
|
+
**Wire format:**
|
|
83
|
+
|
|
84
|
+
```
|
|
85
|
+
Agent receives extractionHint:
|
|
86
|
+
{ recommendedTool: "markitdown", mcpToolName: "convert_to_markdown", uri: "ipfs://Qm..." }
|
|
87
|
+
|
|
88
|
+
Agent calls peer MCP:
|
|
89
|
+
convert_to_markdown(uri="ipfs://Qm...")
|
|
90
|
+
```
|
|
91
|
+
|
|
92
|
+
The `uri` field accepts `http:`, `https:`, `ipfs:`, `file:`, and `data:` schemes.
|
|
93
|
+
|
|
94
|
+
Install: https://github.com/microsoft/markitdown/tree/main/packages/markitdown-mcp
|
|
95
|
+
|
|
96
|
+
```
|
|
97
|
+
pip install markitdown-mcp
|
|
98
|
+
```
|
|
99
|
+
|
|
100
|
+
## Common gotchas
|
|
101
|
+
|
|
102
|
+
- **Chain-specificity:** Court and arbitrable addresses differ between `ethereum` and `gnosis`. Always pass `--chain` when querying a specific dispute, court, or arbitrable contract. Without `--chain`, the default is `ethereum`.
|
|
103
|
+
- **v1/v2 boundary:** `dispute policy`, `dispute brief`, and `arbitrable classify` are **v1 only**. Running any of these on `--chain arbitrum-sepolia` returns `CHAIN_NOT_SUPPORTED`. `dispute get`, `dispute list`, and `evidence list` work on all chains including v2.
|
|
104
|
+
- **`category` is advisory, not structural:** `arbitrable classify` may return a free-text `category` field under `curationSource: 'ipfs-category'`. Never branch logic on this value — it is derived from unstructured Meta-Evidence and is advisory only.
|
|
105
|
+
- **Intersection rule:** Court policy and dispute policy are **both required**. `court policy` defines the court-wide rules; `dispute policy` adds dispute-specific constraints. The effective juror constraint is their intersection (court ∩ dispute). Running only one will miss critical context.
|
|
106
|
+
- **Ruling labels may be absent:** `dispute get` returns `rulingOptions.titles[]` and `rulingLabel` after Meta-Evidence enrichment. These fields may be absent if the dispute has no associated Meta-Evidence or if enrichment fails. Treat them as optional in downstream logic.
|
|
107
|
+
- **`dispute get` is live on-chain; `dispute list` is a subgraph snapshot:** `dispute get`'s `ruling`/`ruled` fields are read directly from the arbitrator contract (`currentRuling()`/`disputes()`), on both v1 and v2, on every invocation shape (`--chain` or the default multi-chain probe) — authoritative arbitrator-side state. `dispute list` items stay subgraph-sourced for performance and may show v2 `ruled` flipping up to ~2h before the arbitrable contract is actually notified (the subgraph's `ruled` is set when the `Ruling` event fires inside `passPeriod`, which is the arbitrator-side period transition — not when `executeRuling()` is later called to notify the arbitrable). Use `dispute get` when the ruling value must be current.
|
|
108
|
+
- **Dead IPFS content surfaces as `IPFS_UNAVAILABLE`, not a generic error:** when `cdn.kleros.link` times out or returns a 5xx for a specific CID, `evidence list` (per-item `error` field), `dispute policy`, and `dispute brief` (per-section `error.code`) all report the distinct `IPFS_UNAVAILABLE` code instead of the generic `IPFS_ERROR`/`TIMEOUT_ERROR`. This is a single-attempt classification — AgentKit does not retry internally and does not try a second gateway (a client-side fallback would mask the signal that the content genuinely needs repinning, since `cdn.kleros.link` already fans out server-side). If you see `IPFS_UNAVAILABLE`, retrying later may help (transient failures do happen); a gateway swap will not.
|
|
109
|
+
- **`dispute brief` is single-chain by design, not a bug:** it always returns `CHAIN_REQUIRED` with an actionable CTA when invoked without an explicit `--chain`. There is no multi-chain fan-out for `dispute brief` — pass `--chain ethereum` or `--chain gnosis` explicitly. This mirrors the v1/v2 boundary bullet above: a deliberate design constraint, not something to route around.
|
|
110
|
+
|
|
111
|
+
## Feedback
|
|
112
|
+
|
|
113
|
+
When something unexpected happens — unknown script CID, missing ruling labels, wrong arbitrable classification, or evidence content that does not load — file a feedback report:
|
|
114
|
+
|
|
115
|
+
```
|
|
116
|
+
kleros report-issue create --type mcp-gap --description "Dispute 797 on gnosis: evidence item QmXxx returned no extractionHint but file extension is .pdf"
|
|
117
|
+
```
|
|
118
|
+
|
|
119
|
+
Use `--submit` to post directly to GitHub Issues (requires `$GITHUB_TOKEN`):
|
|
120
|
+
|
|
121
|
+
```
|
|
122
|
+
kleros report-issue create --type mcp-gap --description "..." --submit
|
|
123
|
+
```
|
|
124
|
+
|
|
125
|
+
Reports are stored locally as `.md` files and optionally posted upstream. Filing a report helps improve coverage in future AgentKit releases.
|