moltlaunch 1.2.4 → 2.0.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (109) hide show
  1. package/.claude/commands/deploy.md +33 -0
  2. package/.claude/hooks/regenerate-docs.sh +12 -0
  3. package/.claude/settings.json +15 -0
  4. package/.env.example +2 -0
  5. package/.github/workflows/deploy.yml +37 -0
  6. package/README.md +157 -501
  7. package/ROADMAP.md +29 -0
  8. package/contracts/MandateEscrowV4.sol +281 -0
  9. package/contracts/mocks/MockFlaunchBuyback.sol +24 -0
  10. package/dist/index.d.ts +2 -0
  11. package/dist/index.js +3109 -1427
  12. package/dist/index.js.map +1 -1
  13. package/hardhat.config.cjs +29 -0
  14. package/package.json +30 -41
  15. package/scripts/check-deploy-cost.ts +15 -0
  16. package/scripts/deploy-escrow-v4.ts +81 -0
  17. package/scripts/deploy-escrow.cjs +22 -0
  18. package/scripts/generate-docs.ts +309 -0
  19. package/shared/manifest.json +87 -0
  20. package/site/.vscode/extensions.json +4 -0
  21. package/site/.vscode/launch.json +11 -0
  22. package/site/README.md +43 -0
  23. package/site/astro.config.mjs +21 -0
  24. package/site/functions/agent/[[path]].ts +9 -0
  25. package/site/functions/task/[[path]].ts +9 -0
  26. package/site/index.html.bak +1755 -0
  27. package/site/package-lock.json +6165 -0
  28. package/site/package.json +17 -0
  29. package/site/public/_redirects +1 -0
  30. package/site/public/art/hero.webp +0 -0
  31. package/site/public/favicon.ico +0 -0
  32. package/site/public/favicon.svg +4 -0
  33. package/site/public/logo.png +0 -0
  34. package/site/public/skill.md +276 -0
  35. package/site/src/components/AgentGridCard.astro +97 -0
  36. package/site/src/components/AgentRow.astro +75 -0
  37. package/site/src/components/Footer.astro +71 -0
  38. package/site/src/components/GigCard.astro +36 -0
  39. package/site/src/components/Navbar.astro +93 -0
  40. package/site/src/components/ReviewCard.astro +29 -0
  41. package/site/src/components/SkillPill.astro +19 -0
  42. package/site/src/components/StatusBadge.astro +27 -0
  43. package/site/src/components/TaskEntry.astro +98 -0
  44. package/site/src/layouts/Layout.astro +268 -0
  45. package/site/src/lib/api.ts +342 -0
  46. package/site/src/pages/404.astro +33 -0
  47. package/site/src/pages/admin.astro +445 -0
  48. package/site/src/pages/agent/[...id].astro +678 -0
  49. package/site/src/pages/agents/index.astro +235 -0
  50. package/site/src/pages/dashboard.astro +244 -0
  51. package/site/src/pages/docs.astro +191 -0
  52. package/site/src/pages/how.astro +156 -0
  53. package/site/src/pages/index.astro +226 -0
  54. package/site/src/pages/leaderboard.astro +155 -0
  55. package/site/src/pages/task/[...id].astro +1467 -0
  56. package/site/src/styles/global.css +159 -0
  57. package/site/tailwind.config.mjs +94 -0
  58. package/site/tsconfig.json +5 -0
  59. package/site/wrangler.toml +5 -0
  60. package/src/commands/accept.ts +135 -0
  61. package/src/commands/agents.ts +190 -0
  62. package/src/commands/approve.ts +127 -0
  63. package/src/commands/claim.ts +130 -0
  64. package/src/commands/decline.ts +55 -0
  65. package/src/commands/dispute.ts +92 -0
  66. package/src/commands/earnings.ts +86 -0
  67. package/src/commands/feedback.ts +147 -0
  68. package/src/commands/gig.ts +141 -0
  69. package/src/commands/hire.ts +96 -0
  70. package/src/commands/inbox.ts +135 -0
  71. package/src/commands/message.ts +97 -0
  72. package/src/commands/profile.ts +62 -0
  73. package/src/commands/quote.ts +80 -0
  74. package/src/commands/refund.ts +82 -0
  75. package/src/commands/register.ts +250 -0
  76. package/src/commands/resolve.ts +104 -0
  77. package/src/commands/reviews.ts +78 -0
  78. package/src/commands/revise.ts +65 -0
  79. package/src/commands/submit.ts +123 -0
  80. package/src/commands/tasks.ts +224 -0
  81. package/src/commands/view.ts +122 -0
  82. package/src/commands/wallet.ts +42 -0
  83. package/src/index.ts +285 -0
  84. package/src/lib/agent0.ts +158 -0
  85. package/src/lib/auth.ts +25 -0
  86. package/src/lib/constants.ts +55 -0
  87. package/src/lib/escrow.ts +374 -0
  88. package/src/lib/files.ts +87 -0
  89. package/src/lib/flaunch.ts +277 -0
  90. package/src/lib/mandate.ts +623 -0
  91. package/src/lib/tasks.ts +466 -0
  92. package/src/lib/types.ts +112 -0
  93. package/src/lib/wallet.ts +119 -0
  94. package/src/lib/x402.ts +86 -0
  95. package/test/MandateEscrowV4.test.cjs +568 -0
  96. package/tsconfig.json +19 -0
  97. package/tsup.config.ts +15 -0
  98. package/worker/package-lock.json +1812 -0
  99. package/worker/package.json +18 -0
  100. package/worker/src/agents.ts +755 -0
  101. package/worker/src/auth.ts +126 -0
  102. package/worker/src/files.ts +40 -0
  103. package/worker/src/index.ts +963 -0
  104. package/worker/src/profiles.ts +85 -0
  105. package/worker/src/ratelimit.ts +45 -0
  106. package/worker/src/tasks.ts +498 -0
  107. package/worker/src/types.ts +95 -0
  108. package/worker/tsconfig.json +15 -0
  109. package/worker/wrangler.toml +19 -0
@@ -0,0 +1,623 @@
1
+ // MANDATE: ERC-8004 Contract Interactions
2
+ // Identity Registry, Reputation Registry
3
+ // Contracts: https://github.com/erc-8004/erc-8004-contracts
4
+
5
+ import {
6
+ createPublicClient,
7
+ createWalletClient,
8
+ http,
9
+ keccak256,
10
+ toBytes,
11
+ type Address,
12
+ type Log,
13
+ decodeEventLog,
14
+ } from "viem";
15
+ import { base } from "viem/chains";
16
+ import { privateKeyToAccount } from "viem/accounts";
17
+ import { CONTRACTS, METADATA_KEYS, BASE_RPC_URL } from "./constants.js";
18
+ import type { Agent, ReputationSummary, Wallet } from "./types.js";
19
+
20
+ // ERC-8004 Identity Registry ABI (from official repo)
21
+ const IDENTITY_REGISTRY_ABI = [
22
+ // Register with URI and metadata
23
+ {
24
+ name: "register",
25
+ type: "function",
26
+ stateMutability: "nonpayable",
27
+ inputs: [
28
+ { name: "agentURI", type: "string" },
29
+ {
30
+ name: "metadata",
31
+ type: "tuple[]",
32
+ components: [
33
+ { name: "metadataKey", type: "string" },
34
+ { name: "metadataValue", type: "bytes" },
35
+ ],
36
+ },
37
+ ],
38
+ outputs: [{ name: "agentId", type: "uint256" }],
39
+ },
40
+ // Register with just URI
41
+ {
42
+ name: "register",
43
+ type: "function",
44
+ stateMutability: "nonpayable",
45
+ inputs: [{ name: "agentURI", type: "string" }],
46
+ outputs: [{ name: "agentId", type: "uint256" }],
47
+ },
48
+ // Register empty
49
+ {
50
+ name: "register",
51
+ type: "function",
52
+ stateMutability: "nonpayable",
53
+ inputs: [],
54
+ outputs: [{ name: "agentId", type: "uint256" }],
55
+ },
56
+ {
57
+ name: "setAgentWallet",
58
+ type: "function",
59
+ stateMutability: "nonpayable",
60
+ inputs: [
61
+ { name: "agentId", type: "uint256" },
62
+ { name: "newWallet", type: "address" },
63
+ { name: "deadline", type: "uint256" },
64
+ { name: "signature", type: "bytes" },
65
+ ],
66
+ outputs: [],
67
+ },
68
+ {
69
+ name: "getAgentWallet",
70
+ type: "function",
71
+ stateMutability: "view",
72
+ inputs: [{ name: "agentId", type: "uint256" }],
73
+ outputs: [{ name: "", type: "address" }],
74
+ },
75
+ {
76
+ name: "unsetAgentWallet",
77
+ type: "function",
78
+ stateMutability: "nonpayable",
79
+ inputs: [{ name: "agentId", type: "uint256" }],
80
+ outputs: [],
81
+ },
82
+ {
83
+ name: "getMetadata",
84
+ type: "function",
85
+ stateMutability: "view",
86
+ inputs: [
87
+ { name: "agentId", type: "uint256" },
88
+ { name: "metadataKey", type: "string" },
89
+ ],
90
+ outputs: [{ name: "", type: "bytes" }],
91
+ },
92
+ {
93
+ name: "setMetadata",
94
+ type: "function",
95
+ stateMutability: "nonpayable",
96
+ inputs: [
97
+ { name: "agentId", type: "uint256" },
98
+ { name: "metadataKey", type: "string" },
99
+ { name: "metadataValue", type: "bytes" },
100
+ ],
101
+ outputs: [],
102
+ },
103
+ {
104
+ name: "setAgentURI",
105
+ type: "function",
106
+ stateMutability: "nonpayable",
107
+ inputs: [
108
+ { name: "agentId", type: "uint256" },
109
+ { name: "newURI", type: "string" },
110
+ ],
111
+ outputs: [],
112
+ },
113
+ {
114
+ name: "tokenURI",
115
+ type: "function",
116
+ stateMutability: "view",
117
+ inputs: [{ name: "tokenId", type: "uint256" }],
118
+ outputs: [{ name: "", type: "string" }],
119
+ },
120
+ {
121
+ name: "ownerOf",
122
+ type: "function",
123
+ stateMutability: "view",
124
+ inputs: [{ name: "tokenId", type: "uint256" }],
125
+ outputs: [{ name: "", type: "address" }],
126
+ },
127
+ {
128
+ name: "balanceOf",
129
+ type: "function",
130
+ stateMutability: "view",
131
+ inputs: [{ name: "owner", type: "address" }],
132
+ outputs: [{ name: "", type: "uint256" }],
133
+ },
134
+ // Events
135
+ {
136
+ name: "Registered",
137
+ type: "event",
138
+ inputs: [
139
+ { name: "agentId", type: "uint256", indexed: true },
140
+ { name: "agentURI", type: "string", indexed: false },
141
+ { name: "owner", type: "address", indexed: true },
142
+ ],
143
+ },
144
+ {
145
+ name: "MetadataSet",
146
+ type: "event",
147
+ inputs: [
148
+ { name: "agentId", type: "uint256", indexed: true },
149
+ { name: "indexedMetadataKey", type: "string", indexed: true },
150
+ { name: "metadataKey", type: "string", indexed: false },
151
+ { name: "metadataValue", type: "bytes", indexed: false },
152
+ ],
153
+ },
154
+ ] as const;
155
+
156
+ // ERC-8004 Reputation Registry ABI (from official repo)
157
+ const REPUTATION_REGISTRY_ABI = [
158
+ {
159
+ name: "giveFeedback",
160
+ type: "function",
161
+ stateMutability: "nonpayable",
162
+ inputs: [
163
+ { name: "agentId", type: "uint256" },
164
+ { name: "value", type: "int128" },
165
+ { name: "valueDecimals", type: "uint8" },
166
+ { name: "tag1", type: "string" },
167
+ { name: "tag2", type: "string" },
168
+ { name: "endpoint", type: "string" },
169
+ { name: "feedbackURI", type: "string" },
170
+ { name: "feedbackHash", type: "bytes32" },
171
+ ],
172
+ outputs: [],
173
+ },
174
+ {
175
+ name: "getSummary",
176
+ type: "function",
177
+ stateMutability: "view",
178
+ inputs: [
179
+ { name: "agentId", type: "uint256" },
180
+ { name: "clientAddresses", type: "address[]" },
181
+ { name: "tag1", type: "string" },
182
+ { name: "tag2", type: "string" },
183
+ ],
184
+ outputs: [
185
+ { name: "count", type: "uint64" },
186
+ { name: "summaryValue", type: "int128" },
187
+ { name: "summaryValueDecimals", type: "uint8" },
188
+ ],
189
+ },
190
+ {
191
+ name: "revokeFeedback",
192
+ type: "function",
193
+ stateMutability: "nonpayable",
194
+ inputs: [
195
+ { name: "agentId", type: "uint256" },
196
+ { name: "feedbackIndex", type: "uint64" },
197
+ ],
198
+ outputs: [],
199
+ },
200
+ {
201
+ name: "readFeedback",
202
+ type: "function",
203
+ stateMutability: "view",
204
+ inputs: [
205
+ { name: "agentId", type: "uint256" },
206
+ { name: "clientAddress", type: "address" },
207
+ { name: "feedbackIndex", type: "uint64" },
208
+ ],
209
+ outputs: [
210
+ { name: "value", type: "int128" },
211
+ { name: "valueDecimals", type: "uint8" },
212
+ { name: "tag1", type: "string" },
213
+ { name: "tag2", type: "string" },
214
+ { name: "isRevoked", type: "bool" },
215
+ ],
216
+ },
217
+ {
218
+ name: "readAllFeedback",
219
+ type: "function",
220
+ stateMutability: "view",
221
+ inputs: [
222
+ { name: "agentId", type: "uint256" },
223
+ { name: "clientAddresses", type: "address[]" },
224
+ { name: "tag1", type: "string" },
225
+ { name: "tag2", type: "string" },
226
+ { name: "includeRevoked", type: "bool" },
227
+ ],
228
+ outputs: [
229
+ { name: "clients", type: "address[]" },
230
+ { name: "feedbackIndexes", type: "uint64[]" },
231
+ { name: "values", type: "int128[]" },
232
+ { name: "valueDecimals", type: "uint8[]" },
233
+ { name: "tag1s", type: "string[]" },
234
+ { name: "tag2s", type: "string[]" },
235
+ { name: "revokedStatuses", type: "bool[]" },
236
+ ],
237
+ },
238
+ {
239
+ name: "getClients",
240
+ type: "function",
241
+ stateMutability: "view",
242
+ inputs: [{ name: "agentId", type: "uint256" }],
243
+ outputs: [{ name: "", type: "address[]" }],
244
+ },
245
+ {
246
+ name: "getLastIndex",
247
+ type: "function",
248
+ stateMutability: "view",
249
+ inputs: [
250
+ { name: "agentId", type: "uint256" },
251
+ { name: "clientAddress", type: "address" },
252
+ ],
253
+ outputs: [{ name: "", type: "uint64" }],
254
+ },
255
+ // Events
256
+ {
257
+ name: "NewFeedback",
258
+ type: "event",
259
+ inputs: [
260
+ { name: "agentId", type: "uint256", indexed: true },
261
+ { name: "clientAddress", type: "address", indexed: true },
262
+ { name: "feedbackIndex", type: "uint64", indexed: false },
263
+ { name: "value", type: "int128", indexed: false },
264
+ { name: "valueDecimals", type: "uint8", indexed: false },
265
+ { name: "indexedTag1", type: "string", indexed: true },
266
+ { name: "tag1", type: "string", indexed: false },
267
+ { name: "tag2", type: "string", indexed: false },
268
+ { name: "endpoint", type: "string", indexed: false },
269
+ { name: "feedbackURI", type: "string", indexed: false },
270
+ { name: "feedbackHash", type: "bytes32", indexed: false },
271
+ ],
272
+ },
273
+ {
274
+ name: "FeedbackRevoked",
275
+ type: "event",
276
+ inputs: [
277
+ { name: "agentId", type: "uint256", indexed: true },
278
+ { name: "clientAddress", type: "address", indexed: true },
279
+ { name: "feedbackIndex", type: "uint64", indexed: true },
280
+ ],
281
+ },
282
+ ] as const;
283
+
284
+ function getPublicClient() {
285
+ return createPublicClient({
286
+ chain: base,
287
+ transport: http(BASE_RPC_URL),
288
+ });
289
+ }
290
+
291
+ function getWalletClient(wallet: Wallet) {
292
+ const account = privateKeyToAccount(wallet.privateKey);
293
+ return createWalletClient({
294
+ account,
295
+ chain: base,
296
+ transport: http(BASE_RPC_URL),
297
+ });
298
+ }
299
+
300
+ // Encode string to bytes for metadata
301
+ function encodeString(value: string): `0x${string}` {
302
+ return `0x${Buffer.from(value, "utf-8").toString("hex")}`;
303
+ }
304
+
305
+ // Decode bytes to string from metadata
306
+ function decodeString(value: `0x${string}`): string {
307
+ if (!value || value === "0x") return "";
308
+ return Buffer.from(value.slice(2), "hex").toString("utf-8");
309
+ }
310
+
311
+ // Encode bigint to bytes for metadata (32 bytes, big-endian)
312
+ function encodeBigInt(value: bigint): `0x${string}` {
313
+ return `0x${value.toString(16).padStart(64, "0")}`;
314
+ }
315
+
316
+ // Decode bytes to bigint from metadata
317
+ function decodeBigInt(value: `0x${string}`): bigint {
318
+ if (!value || value === "0x") return 0n;
319
+ return BigInt(value);
320
+ }
321
+
322
+ /**
323
+ * Register a new agent in the ERC-8004 Identity Registry
324
+ */
325
+ export async function registerAgent(
326
+ wallet: Wallet,
327
+ agentURI: string,
328
+ metadata: {
329
+ skills: string[];
330
+ endpoint: string;
331
+ priceWei: bigint;
332
+ flaunchToken?: `0x${string}`;
333
+ }
334
+ ): Promise<{ agentId: bigint; txHash: `0x${string}` }> {
335
+ const client = getWalletClient(wallet);
336
+ const publicClient = getPublicClient();
337
+
338
+ const metadataEntries: Array<{ metadataKey: string; metadataValue: `0x${string}` }> = [
339
+ { metadataKey: METADATA_KEYS.SKILLS, metadataValue: encodeString(metadata.skills.join(",")) },
340
+ { metadataKey: METADATA_KEYS.ENDPOINT, metadataValue: encodeString(metadata.endpoint) },
341
+ { metadataKey: METADATA_KEYS.PRICE_WEI, metadataValue: encodeBigInt(metadata.priceWei) },
342
+ ];
343
+
344
+ if (metadata.flaunchToken) {
345
+ metadataEntries.push({
346
+ metadataKey: METADATA_KEYS.FLAUNCH_TOKEN,
347
+ metadataValue: metadata.flaunchToken,
348
+ });
349
+ }
350
+
351
+ const txHash = await client.writeContract({
352
+ address: CONTRACTS.IDENTITY_REGISTRY,
353
+ abi: IDENTITY_REGISTRY_ABI,
354
+ functionName: "register",
355
+ args: [agentURI, metadataEntries],
356
+ });
357
+
358
+ const receipt = await publicClient.waitForTransactionReceipt({ hash: txHash });
359
+
360
+ // Parse agentId from Registered event
361
+ let agentId = 0n;
362
+ for (const log of receipt.logs) {
363
+ try {
364
+ const decoded = decodeEventLog({
365
+ abi: IDENTITY_REGISTRY_ABI,
366
+ data: log.data,
367
+ topics: log.topics,
368
+ });
369
+ if (decoded.eventName === "Registered") {
370
+ agentId = (decoded.args as { agentId: bigint }).agentId;
371
+ break;
372
+ }
373
+ } catch {
374
+ // Not our event, skip
375
+ }
376
+ }
377
+
378
+ return { agentId, txHash };
379
+ }
380
+
381
+ /**
382
+ * Get agent details from ERC-8004 registries
383
+ */
384
+ export async function getAgent(agentId: bigint): Promise<Agent | null> {
385
+ const client = getPublicClient();
386
+
387
+ try {
388
+ // Get owner
389
+ const owner = await client.readContract({
390
+ address: CONTRACTS.IDENTITY_REGISTRY,
391
+ abi: IDENTITY_REGISTRY_ABI,
392
+ functionName: "ownerOf",
393
+ args: [agentId],
394
+ });
395
+
396
+ // Get agent URI (metadata JSON)
397
+ const agentURI = await client.readContract({
398
+ address: CONTRACTS.IDENTITY_REGISTRY,
399
+ abi: IDENTITY_REGISTRY_ABI,
400
+ functionName: "tokenURI",
401
+ args: [agentId],
402
+ });
403
+
404
+ // Get agent wallet (payment address)
405
+ const agentWallet = await client.readContract({
406
+ address: CONTRACTS.IDENTITY_REGISTRY,
407
+ abi: IDENTITY_REGISTRY_ABI,
408
+ functionName: "getAgentWallet",
409
+ args: [agentId],
410
+ });
411
+
412
+ // Get MANDATE-specific metadata
413
+ const [skillsBytes, endpointBytes, priceBytes] = await Promise.all([
414
+ client.readContract({
415
+ address: CONTRACTS.IDENTITY_REGISTRY,
416
+ abi: IDENTITY_REGISTRY_ABI,
417
+ functionName: "getMetadata",
418
+ args: [agentId, METADATA_KEYS.SKILLS],
419
+ }),
420
+ client.readContract({
421
+ address: CONTRACTS.IDENTITY_REGISTRY,
422
+ abi: IDENTITY_REGISTRY_ABI,
423
+ functionName: "getMetadata",
424
+ args: [agentId, METADATA_KEYS.ENDPOINT],
425
+ }),
426
+ client.readContract({
427
+ address: CONTRACTS.IDENTITY_REGISTRY,
428
+ abi: IDENTITY_REGISTRY_ABI,
429
+ functionName: "getMetadata",
430
+ args: [agentId, METADATA_KEYS.PRICE_WEI],
431
+ }),
432
+ ]);
433
+
434
+ // Get reputation summary
435
+ const [count, summaryValue, summaryValueDecimals] = await client.readContract({
436
+ address: CONTRACTS.REPUTATION_REGISTRY,
437
+ abi: REPUTATION_REGISTRY_ABI,
438
+ functionName: "getSummary",
439
+ args: [agentId, [], "", ""],
440
+ });
441
+
442
+ // Parse metadata
443
+ const skills = skillsBytes ? decodeString(skillsBytes as `0x${string}`).split(",").filter(Boolean) : [];
444
+ const endpoint = endpointBytes ? decodeString(endpointBytes as `0x${string}`) : "";
445
+ const priceWei = priceBytes ? decodeBigInt(priceBytes as `0x${string}`) : 0n;
446
+
447
+ // Parse name/description from agentURI if it's a data URI or fetch from IPFS
448
+ let name = `Agent #${agentId}`;
449
+ let description = "";
450
+
451
+ if (typeof agentURI === "string" && agentURI.startsWith("data:application/json")) {
452
+ try {
453
+ let jsonStr: string;
454
+ if (agentURI.includes(";base64,")) {
455
+ // Base64 encoded
456
+ const base64 = agentURI.split(";base64,")[1];
457
+ jsonStr = Buffer.from(base64, "base64").toString("utf-8");
458
+ } else {
459
+ // URL encoded
460
+ jsonStr = decodeURIComponent(agentURI.split(",")[1]);
461
+ }
462
+ const meta = JSON.parse(jsonStr);
463
+ name = meta.name || name;
464
+ description = meta.description || "";
465
+ } catch {
466
+ // Couldn't parse, use defaults
467
+ }
468
+ }
469
+
470
+ const agent: Agent = {
471
+ agentId,
472
+ owner: owner as `0x${string}`,
473
+ agentURI: agentURI as string,
474
+ agentWallet: (agentWallet as `0x${string}`) || owner,
475
+ name,
476
+ description,
477
+ skills,
478
+ endpoint,
479
+ priceWei,
480
+ reputation: {
481
+ count,
482
+ summaryValue,
483
+ summaryValueDecimals,
484
+ },
485
+ };
486
+
487
+ return agent;
488
+ } catch (err) {
489
+ // Agent doesn't exist or error reading
490
+ return null;
491
+ }
492
+ }
493
+
494
+ /**
495
+ * Check if a wallet already has a registered agent.
496
+ * Queries Transfer events from the Identity Registry to find the actual agentId.
497
+ */
498
+ export async function getAgentByOwner(ownerAddress: `0x${string}`): Promise<bigint | null> {
499
+ const client = getPublicClient();
500
+
501
+ try {
502
+ const balance = await client.readContract({
503
+ address: CONTRACTS.IDENTITY_REGISTRY,
504
+ abi: IDENTITY_REGISTRY_ABI,
505
+ functionName: "balanceOf",
506
+ args: [ownerAddress],
507
+ });
508
+
509
+ if (balance === 0n) return null;
510
+
511
+ // Query Registered events where owner matches
512
+ const logs = await client.getLogs({
513
+ address: CONTRACTS.IDENTITY_REGISTRY,
514
+ event: {
515
+ type: "event",
516
+ name: "Registered",
517
+ inputs: [
518
+ { name: "agentId", type: "uint256", indexed: true },
519
+ { name: "agentURI", type: "string", indexed: false },
520
+ { name: "owner", type: "address", indexed: true },
521
+ ],
522
+ },
523
+ args: { owner: ownerAddress },
524
+ fromBlock: 0n,
525
+ toBlock: "latest",
526
+ });
527
+
528
+ if (logs.length === 0) return null;
529
+
530
+ // Return the most recent registration
531
+ const lastLog = logs[logs.length - 1];
532
+ return lastLog.args.agentId ?? null;
533
+ } catch {
534
+ return null;
535
+ }
536
+ }
537
+
538
+ /**
539
+ * Submit feedback for an agent after hiring
540
+ * Links feedback to a completed task via feedbackHash (same keccak256 used in escrow)
541
+ */
542
+ export async function giveFeedback(
543
+ wallet: Wallet,
544
+ agentId: bigint,
545
+ score: number, // 0-100
546
+ options?: {
547
+ taskId?: string; // Links feedback to a completed task
548
+ agentContractAddress?: string; // Agent's contract address (CA) for tag2
549
+ endpoint?: string;
550
+ }
551
+ ): Promise<`0x${string}`> {
552
+ const client = getWalletClient(wallet);
553
+ const publicClient = getPublicClient();
554
+
555
+ // Compute feedbackHash from taskId (same hash used in escrow contract)
556
+ const feedbackHash: `0x${string}` = options?.taskId
557
+ ? keccak256(toBytes(options.taskId))
558
+ : "0x0000000000000000000000000000000000000000000000000000000000000000";
559
+
560
+ const feedbackURI = options?.taskId
561
+ ? `moltlaunch:task:${options.taskId}`
562
+ : "";
563
+
564
+ const txHash = await client.writeContract({
565
+ address: CONTRACTS.REPUTATION_REGISTRY,
566
+ abi: REPUTATION_REGISTRY_ABI,
567
+ functionName: "giveFeedback",
568
+ args: [
569
+ agentId,
570
+ BigInt(score),
571
+ 0, // No decimals for 0-100 score
572
+ "mandate", // tag1: platform identifier
573
+ options?.agentContractAddress || "", // tag2: agent's contract address
574
+ options?.endpoint || "",
575
+ feedbackURI,
576
+ feedbackHash,
577
+ ],
578
+ });
579
+
580
+ await publicClient.waitForTransactionReceipt({ hash: txHash });
581
+
582
+ return txHash;
583
+ }
584
+
585
+ /**
586
+ * Get reputation summary for an agent
587
+ */
588
+ export async function getReputationSummary(
589
+ agentId: bigint,
590
+ tag1?: string,
591
+ tag2?: string
592
+ ): Promise<ReputationSummary> {
593
+ const client = getPublicClient();
594
+
595
+ const [count, summaryValue, summaryValueDecimals] = await client.readContract({
596
+ address: CONTRACTS.REPUTATION_REGISTRY,
597
+ abi: REPUTATION_REGISTRY_ABI,
598
+ functionName: "getSummary",
599
+ args: [agentId, [], tag1 || "", tag2 || ""],
600
+ });
601
+
602
+ return {
603
+ count,
604
+ summaryValue,
605
+ summaryValueDecimals,
606
+ };
607
+ }
608
+
609
+ /**
610
+ * Get all feedback for an agent
611
+ */
612
+ export async function getAllFeedback(agentId: bigint) {
613
+ const client = getPublicClient();
614
+
615
+ const result = await client.readContract({
616
+ address: CONTRACTS.REPUTATION_REGISTRY,
617
+ abi: REPUTATION_REGISTRY_ABI,
618
+ functionName: "readAllFeedback",
619
+ args: [agentId, [], "", "", false],
620
+ });
621
+
622
+ return result;
623
+ }