moltlaunch 2.0.0 → 2.0.2

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 (108) hide show
  1. package/README.md +2 -2
  2. package/dist/index.js +18 -18
  3. package/dist/index.js.map +1 -1
  4. package/package.json +6 -2
  5. package/.claude/commands/deploy.md +0 -33
  6. package/.claude/hooks/regenerate-docs.sh +0 -12
  7. package/.claude/settings.json +0 -15
  8. package/.env.example +0 -2
  9. package/.github/workflows/deploy.yml +0 -37
  10. package/ROADMAP.md +0 -29
  11. package/contracts/MandateEscrowV4.sol +0 -281
  12. package/contracts/mocks/MockFlaunchBuyback.sol +0 -24
  13. package/hardhat.config.cjs +0 -29
  14. package/scripts/check-deploy-cost.ts +0 -15
  15. package/scripts/deploy-escrow-v4.ts +0 -81
  16. package/scripts/deploy-escrow.cjs +0 -22
  17. package/scripts/generate-docs.ts +0 -309
  18. package/shared/manifest.json +0 -87
  19. package/site/.vscode/extensions.json +0 -4
  20. package/site/.vscode/launch.json +0 -11
  21. package/site/README.md +0 -43
  22. package/site/astro.config.mjs +0 -21
  23. package/site/functions/agent/[[path]].ts +0 -9
  24. package/site/functions/task/[[path]].ts +0 -9
  25. package/site/index.html.bak +0 -1755
  26. package/site/package-lock.json +0 -6165
  27. package/site/package.json +0 -17
  28. package/site/public/_redirects +0 -1
  29. package/site/public/art/hero.webp +0 -0
  30. package/site/public/favicon.ico +0 -0
  31. package/site/public/favicon.svg +0 -4
  32. package/site/public/logo.png +0 -0
  33. package/site/public/skill.md +0 -276
  34. package/site/src/components/AgentGridCard.astro +0 -97
  35. package/site/src/components/AgentRow.astro +0 -75
  36. package/site/src/components/Footer.astro +0 -71
  37. package/site/src/components/GigCard.astro +0 -36
  38. package/site/src/components/Navbar.astro +0 -93
  39. package/site/src/components/ReviewCard.astro +0 -29
  40. package/site/src/components/SkillPill.astro +0 -19
  41. package/site/src/components/StatusBadge.astro +0 -27
  42. package/site/src/components/TaskEntry.astro +0 -98
  43. package/site/src/layouts/Layout.astro +0 -268
  44. package/site/src/lib/api.ts +0 -342
  45. package/site/src/pages/404.astro +0 -33
  46. package/site/src/pages/admin.astro +0 -445
  47. package/site/src/pages/agent/[...id].astro +0 -678
  48. package/site/src/pages/agents/index.astro +0 -235
  49. package/site/src/pages/dashboard.astro +0 -244
  50. package/site/src/pages/docs.astro +0 -191
  51. package/site/src/pages/how.astro +0 -156
  52. package/site/src/pages/index.astro +0 -226
  53. package/site/src/pages/leaderboard.astro +0 -155
  54. package/site/src/pages/task/[...id].astro +0 -1467
  55. package/site/src/styles/global.css +0 -159
  56. package/site/tailwind.config.mjs +0 -94
  57. package/site/tsconfig.json +0 -5
  58. package/site/wrangler.toml +0 -5
  59. package/src/commands/accept.ts +0 -135
  60. package/src/commands/agents.ts +0 -190
  61. package/src/commands/approve.ts +0 -127
  62. package/src/commands/claim.ts +0 -130
  63. package/src/commands/decline.ts +0 -55
  64. package/src/commands/dispute.ts +0 -92
  65. package/src/commands/earnings.ts +0 -86
  66. package/src/commands/feedback.ts +0 -147
  67. package/src/commands/gig.ts +0 -141
  68. package/src/commands/hire.ts +0 -96
  69. package/src/commands/inbox.ts +0 -135
  70. package/src/commands/message.ts +0 -97
  71. package/src/commands/profile.ts +0 -62
  72. package/src/commands/quote.ts +0 -80
  73. package/src/commands/refund.ts +0 -82
  74. package/src/commands/register.ts +0 -250
  75. package/src/commands/resolve.ts +0 -104
  76. package/src/commands/reviews.ts +0 -78
  77. package/src/commands/revise.ts +0 -65
  78. package/src/commands/submit.ts +0 -123
  79. package/src/commands/tasks.ts +0 -224
  80. package/src/commands/view.ts +0 -122
  81. package/src/commands/wallet.ts +0 -42
  82. package/src/index.ts +0 -285
  83. package/src/lib/agent0.ts +0 -158
  84. package/src/lib/auth.ts +0 -25
  85. package/src/lib/constants.ts +0 -55
  86. package/src/lib/escrow.ts +0 -374
  87. package/src/lib/files.ts +0 -87
  88. package/src/lib/flaunch.ts +0 -277
  89. package/src/lib/mandate.ts +0 -623
  90. package/src/lib/tasks.ts +0 -466
  91. package/src/lib/types.ts +0 -112
  92. package/src/lib/wallet.ts +0 -119
  93. package/src/lib/x402.ts +0 -86
  94. package/test/MandateEscrowV4.test.cjs +0 -568
  95. package/tsconfig.json +0 -19
  96. package/tsup.config.ts +0 -15
  97. package/worker/package-lock.json +0 -1812
  98. package/worker/package.json +0 -18
  99. package/worker/src/agents.ts +0 -755
  100. package/worker/src/auth.ts +0 -126
  101. package/worker/src/files.ts +0 -40
  102. package/worker/src/index.ts +0 -963
  103. package/worker/src/profiles.ts +0 -85
  104. package/worker/src/ratelimit.ts +0 -45
  105. package/worker/src/tasks.ts +0 -498
  106. package/worker/src/types.ts +0 -95
  107. package/worker/tsconfig.json +0 -15
  108. package/worker/wrangler.toml +0 -19
@@ -1,755 +0,0 @@
1
- // ERC-8004 Agent Discovery
2
- // Reads agents from the Identity Registry on Base
3
-
4
- import type { Env } from './types';
5
-
6
- // ERC-8004 Identity Registry on Base
7
- const IDENTITY_REGISTRY = '0x8004A169FB4a3325136EB29fA0ceB6D2e539a432';
8
- const REPUTATION_REGISTRY = '0x8004BAa17C55a88189AE136b182e5fdA19dE9b63';
9
-
10
- // MandateEscrow contracts - V3 for historical data, V4 for new escrows
11
- const ESCROW_V3 = '0x893a388d253dc8509877b1b4529b7eaa17597b40';
12
- const ESCROW_V4 = '0x2c46054b4577b4fcdde28cb613dc2ba4b1127b0c';
13
-
14
- // Flaunch API for token market data
15
- const FLAUNCH_API = 'https://api.flayerlabs.xyz/v1/base';
16
-
17
- // Metadata keys stored in ERC-8004
18
- const METADATA_KEYS = {
19
- FLAUNCH_TOKEN: 'flaunchToken',
20
- SKILLS: 'skills',
21
- ENDPOINT: 'endpoint',
22
- PRICE_WEI: 'priceWei',
23
- };
24
-
25
- // Agent type returned by the API
26
- export interface Agent {
27
- id: string; // agentId as hex string
28
- agentIdBigInt: string; // Original bigint as string for display
29
- owner: string;
30
- agentURI: string;
31
- agentWallet: string;
32
- name: string;
33
- description: string;
34
- skills: string[];
35
- endpoint: string;
36
- priceWei: string;
37
- flaunchToken?: string;
38
- // Reputation from ERC-8004
39
- reputation: {
40
- count: number;
41
- summaryValue: number;
42
- summaryValueDecimals: number;
43
- };
44
- // Market data (Flaunch + DexScreener)
45
- marketCapUSD?: number;
46
- volume24hUSD?: number;
47
- priceChange24h?: number;
48
- liquidityUSD?: number;
49
- holders?: number;
50
- image?: string;
51
- symbol?: string;
52
- flaunchUrl?: string;
53
- // Revenue tracking from escrow contract
54
- totalBurnedETH?: number; // ETH used to buyback & burn tokens
55
- totalBurnedUSD?: number;
56
- totalBurnedTokens?: number; // Tokens sent to burn address
57
- // Stats from worker (included in enriched responses)
58
- completedTasks?: number;
59
- totalEarningsETH?: number;
60
- }
61
-
62
- // Minimal ABI for reading from Identity Registry
63
- const IDENTITY_ABI = {
64
- ownerOf: 'function ownerOf(uint256 tokenId) view returns (address)',
65
- tokenURI: 'function tokenURI(uint256 tokenId) view returns (string)',
66
- getAgentWallet: 'function getAgentWallet(uint256 agentId) view returns (address)',
67
- getMetadata: 'function getMetadata(uint256 agentId, string metadataKey) view returns (bytes)',
68
- balanceOf: 'function balanceOf(address owner) view returns (uint256)',
69
- };
70
-
71
- const REPUTATION_ABI = {
72
- getSummary: 'function getSummary(uint256 agentId, address[] clientAddresses, string tag1, string tag2) view returns (uint64 count, int128 summaryValue, uint8 summaryValueDecimals)',
73
- };
74
-
75
- // Function selectors (keccak256 of signature, first 4 bytes)
76
- const SELECTORS = {
77
- ownerOf: '0x6352211e',
78
- tokenURI: '0xc87b56dd',
79
- getAgentWallet: '0xf3ea1f13', // getAgentWallet(uint256)
80
- getMetadata: '0xcb4799f2', // getMetadata(uint256,string)
81
- getSummary: '0x81bbba58', // getSummary(uint256,address[],string,string)
82
- getClients: '0x42dd519c', // getClients(uint256)
83
- };
84
-
85
- // Encode uint256 for RPC call
86
- function encodeUint256(value: bigint): string {
87
- return value.toString(16).padStart(64, '0');
88
- }
89
-
90
- // Encode string for RPC call (offset + length + data)
91
- function encodeString(str: string): string {
92
- const encoder = new TextEncoder();
93
- const bytes = encoder.encode(str);
94
- let hex = '';
95
- for (const b of bytes) {
96
- hex += b.toString(16).padStart(2, '0');
97
- }
98
- const len = bytes.length.toString(16).padStart(64, '0');
99
- const padded = hex.padEnd(Math.ceil(hex.length / 64) * 64, '0');
100
- return len + padded;
101
- }
102
-
103
- // Decode hex string to UTF-8
104
- function decodeString(hex: string): string {
105
- if (!hex || hex === '0x' || hex === '') return '';
106
- const clean = hex.startsWith('0x') ? hex.slice(2) : hex;
107
- if (clean.length === 0) return '';
108
- const bytes = new Uint8Array(clean.length / 2);
109
- for (let i = 0; i < bytes.length; i++) {
110
- bytes[i] = parseInt(clean.substring(i * 2, i * 2 + 2), 16);
111
- }
112
- const decoder = new TextDecoder('utf-8');
113
- return decoder.decode(bytes).replace(/\0/g, '');
114
- }
115
-
116
- // Decode hex to bigint
117
- function decodeBigInt(hex: string): bigint {
118
- if (!hex || hex === '0x' || hex === '') return 0n;
119
- const clean = hex.startsWith('0x') ? hex.slice(2) : hex;
120
- if (clean.length === 0) return 0n;
121
- return BigInt('0x' + clean);
122
- }
123
-
124
- // Make eth_call
125
- async function ethCall(rpcUrl: string, to: string, data: string): Promise<string> {
126
- console.log('[ethCall] Calling:', to, data.substring(0, 20) + '...');
127
- const res = await fetch(rpcUrl, {
128
- method: 'POST',
129
- headers: { 'Content-Type': 'application/json' },
130
- body: JSON.stringify({
131
- jsonrpc: '2.0',
132
- method: 'eth_call',
133
- params: [{ to, data }, 'latest'],
134
- id: 1,
135
- }),
136
- });
137
- const json = await res.json() as { result?: string; error?: { message: string } };
138
- console.log('[ethCall] Response:', json.error ? 'ERROR: ' + json.error.message : 'OK');
139
- if (json.error) throw new Error(json.error.message);
140
- return json.result || '0x';
141
- }
142
-
143
- // Get ETH balance of an address
144
- async function getBalance(rpcUrl: string, address: string): Promise<number> {
145
- try {
146
- const res = await fetch(rpcUrl, {
147
- method: 'POST',
148
- headers: { 'Content-Type': 'application/json' },
149
- body: JSON.stringify({
150
- jsonrpc: '2.0',
151
- method: 'eth_getBalance',
152
- params: [address, 'latest'],
153
- id: 1,
154
- }),
155
- });
156
- const json = await res.json() as { result?: string; error?: { message: string } };
157
- if (json.error || !json.result) return 0;
158
- return Number(BigInt(json.result)) / 1e18;
159
- } catch {
160
- return 0;
161
- }
162
- }
163
-
164
- // Get total ETH burned for a token from escrow BuybackBurned events
165
- // Event: BuybackBurned(bytes32 indexed taskId, address indexed token, uint256 ethAmount)
166
- async function getTotalBurned(rpcUrl: string, tokenAddress: string): Promise<number> {
167
- const topic0 = '0xd348d018935f8254a4d4a03b887cc0f0a0cdd7c6afd4414feb31b6af57c95bbc';
168
- const topic2 = '0x000000000000000000000000' + tokenAddress.slice(2).toLowerCase();
169
-
170
- let totalWei = 0n;
171
-
172
- // Query from both V3 and V4 contracts
173
- for (const contractAddr of [ESCROW_V3, ESCROW_V4]) {
174
- try {
175
- const res = await fetch(rpcUrl, {
176
- method: 'POST',
177
- headers: { 'Content-Type': 'application/json' },
178
- body: JSON.stringify({
179
- jsonrpc: '2.0',
180
- method: 'eth_getLogs',
181
- params: [{
182
- address: contractAddr,
183
- topics: [topic0, null, topic2],
184
- fromBlock: '0x0',
185
- toBlock: 'latest',
186
- }],
187
- id: 1,
188
- }),
189
- });
190
-
191
- const json = await res.json() as {
192
- result?: Array<{ data: string }>;
193
- error?: { message: string };
194
- };
195
-
196
- if (json.error || !json.result) continue;
197
-
198
- for (const log of json.result) {
199
- const amount = BigInt(log.data);
200
- totalWei += amount;
201
- }
202
- } catch {
203
- // Continue with other contract if one fails
204
- }
205
- }
206
-
207
- return Number(totalWei) / 1e18;
208
- }
209
-
210
- // Get total tokens sent to burn address (0xdead) for a token
211
- // Uses Transfer(from, to, value) events where to=0xdead
212
- async function getTotalTokensBurned(rpcUrl: string, tokenAddress: string): Promise<number> {
213
- // keccak256("Transfer(address,address,uint256)")
214
- const topic0 = '0xddf252ad1be2c89b69c2b068fc378daa952ba7f163c4a11628f55a4df523b3ef';
215
- const deadTopic = '0x000000000000000000000000000000000000000000000000000000000000dead';
216
-
217
- try {
218
- const res = await fetch(rpcUrl, {
219
- method: 'POST',
220
- headers: { 'Content-Type': 'application/json' },
221
- body: JSON.stringify({
222
- jsonrpc: '2.0',
223
- method: 'eth_getLogs',
224
- params: [{
225
- address: tokenAddress,
226
- topics: [topic0, null, deadTopic],
227
- fromBlock: '0x0',
228
- toBlock: 'latest',
229
- }],
230
- id: 1,
231
- }),
232
- });
233
-
234
- const json = await res.json() as {
235
- result?: Array<{ data: string }>;
236
- error?: { message: string };
237
- };
238
-
239
- if (json.error || !json.result) return 0;
240
-
241
- let totalWei = 0n;
242
- for (const log of json.result) {
243
- totalWei += BigInt(log.data);
244
- }
245
-
246
- return Number(totalWei) / 1e18;
247
- } catch {
248
- return 0;
249
- }
250
- }
251
-
252
- // Get logs for Registered events with pagination
253
- async function getRegisteredEvents(rpcUrl: string): Promise<Array<{ agentId: bigint; owner: string }>> {
254
- // Topic0 for Registered event from ERC-8004
255
- const topic0 = '0xca52e62c367d81bb2e328eb795f7c7ba24afb478408a26c0e201d155c449bc4a';
256
-
257
- // Query in chunks to avoid RPC limits (max 10000 logs per query)
258
- const allLogs: Array<{ agentId: bigint; owner: string }> = [];
259
-
260
- // Get current block number
261
- const blockRes = await fetch(rpcUrl, {
262
- method: 'POST',
263
- headers: { 'Content-Type': 'application/json' },
264
- body: JSON.stringify({
265
- jsonrpc: '2.0',
266
- method: 'eth_blockNumber',
267
- params: [],
268
- id: 1,
269
- }),
270
- });
271
- const blockJson = await blockRes.json() as { result?: string };
272
- const latestBlock = parseInt(blockJson.result || '0x0', 16);
273
-
274
- // Query in chunks of 100000 blocks
275
- const chunkSize = 100000;
276
- let fromBlock = 0;
277
-
278
- while (fromBlock < latestBlock) {
279
- const toBlock = Math.min(fromBlock + chunkSize - 1, latestBlock);
280
-
281
- const res = await fetch(rpcUrl, {
282
- method: 'POST',
283
- headers: { 'Content-Type': 'application/json' },
284
- body: JSON.stringify({
285
- jsonrpc: '2.0',
286
- method: 'eth_getLogs',
287
- params: [{
288
- address: IDENTITY_REGISTRY,
289
- topics: [topic0],
290
- fromBlock: '0x' + fromBlock.toString(16),
291
- toBlock: '0x' + toBlock.toString(16),
292
- }],
293
- id: 1,
294
- }),
295
- });
296
-
297
- const json = await res.json() as {
298
- result?: Array<{ topics: string[]; data: string }>;
299
- error?: { message: string };
300
- };
301
-
302
- if (json.error) {
303
- console.log(`[getRegisteredEvents] Error querying blocks ${fromBlock}-${toBlock}:`, json.error.message);
304
- // If we get an error (like too many results), try smaller chunks
305
- if (json.error.message.includes('exceed') || json.error.message.includes('limit')) {
306
- // Skip this range for now
307
- fromBlock = toBlock + 1;
308
- continue;
309
- }
310
- throw new Error(json.error.message);
311
- }
312
-
313
- const logs = json.result || [];
314
- console.log(`[getRegisteredEvents] Found ${logs.length} events in blocks ${fromBlock}-${toBlock}`);
315
-
316
- for (const log of logs) {
317
- allLogs.push({
318
- agentId: BigInt(log.topics[1]),
319
- owner: '0x' + log.topics[2].slice(-40),
320
- });
321
- }
322
-
323
- fromBlock = toBlock + 1;
324
- }
325
-
326
- console.log(`[getRegisteredEvents] Total events: ${allLogs.length}`);
327
- return allLogs;
328
- }
329
-
330
- // Get agent details from ERC-8004
331
- async function getAgentFromContract(rpcUrl: string, agentId: bigint): Promise<Agent | null> {
332
- console.log('[getAgentFromContract] Starting for agentId:', agentId.toString());
333
- try {
334
- const agentIdHex = encodeUint256(agentId);
335
- console.log('[getAgentFromContract] agentIdHex:', agentIdHex);
336
-
337
- // Get owner
338
- const ownerData = SELECTORS.ownerOf + agentIdHex;
339
- console.log('[getAgentFromContract] Calling ownerOf...');
340
- const ownerResult = await ethCall(rpcUrl, IDENTITY_REGISTRY, ownerData);
341
- console.log('[getAgentFromContract] ownerResult:', ownerResult);
342
- const owner = '0x' + ownerResult.slice(-40);
343
- console.log('[getAgentFromContract] owner:', owner);
344
-
345
- // Get tokenURI
346
- const uriData = SELECTORS.tokenURI + agentIdHex;
347
- const uriResult = await ethCall(rpcUrl, IDENTITY_REGISTRY, uriData);
348
- // Decode ABI-encoded string response
349
- let agentURI = '';
350
- if (uriResult && uriResult.length > 130) {
351
- const strOffset = parseInt(uriResult.slice(2, 66), 16) * 2;
352
- const strLen = parseInt(uriResult.slice(66, 130), 16);
353
- agentURI = decodeString(uriResult.slice(130, 130 + strLen * 2));
354
- }
355
-
356
- // Get agent wallet (optional - may not be set)
357
- let agentWallet = owner;
358
- try {
359
- const walletData = SELECTORS.getAgentWallet + agentIdHex;
360
- const walletResult = await ethCall(rpcUrl, IDENTITY_REGISTRY, walletData);
361
- if (walletResult && walletResult !== '0x' + '0'.repeat(64)) {
362
- agentWallet = '0x' + walletResult.slice(-40);
363
- }
364
- } catch {
365
- // Agent wallet not set, use owner
366
- console.log('[getAgentFromContract] No agentWallet set, using owner');
367
- }
368
-
369
- // Get metadata - skills (optional)
370
- let skills: string[] = [];
371
- try {
372
- const skillsKey = encodeString(METADATA_KEYS.SKILLS);
373
- const skillsData = SELECTORS.getMetadata + agentIdHex + '0000000000000000000000000000000000000000000000000000000000000040' + skillsKey;
374
- const skillsResult = await ethCall(rpcUrl, IDENTITY_REGISTRY, skillsData);
375
- if (skillsResult && skillsResult.length > 130) {
376
- const strLen = parseInt(skillsResult.slice(66, 130), 16);
377
- const skillsStr = decodeString(skillsResult.slice(130, 130 + strLen * 2));
378
- skills = skillsStr.split(',').filter(Boolean);
379
- }
380
- } catch {
381
- console.log('[getAgentFromContract] No skills metadata');
382
- }
383
-
384
- // Get metadata - endpoint (optional)
385
- let endpoint = '';
386
- try {
387
- const endpointKey = encodeString(METADATA_KEYS.ENDPOINT);
388
- const endpointData = SELECTORS.getMetadata + agentIdHex + '0000000000000000000000000000000000000000000000000000000000000040' + endpointKey;
389
- const endpointResult = await ethCall(rpcUrl, IDENTITY_REGISTRY, endpointData);
390
- if (endpointResult && endpointResult.length > 130) {
391
- const strLen = parseInt(endpointResult.slice(66, 130), 16);
392
- endpoint = decodeString(endpointResult.slice(130, 130 + strLen * 2));
393
- }
394
- } catch {
395
- console.log('[getAgentFromContract] No endpoint metadata');
396
- }
397
-
398
- // Get metadata - priceWei (optional)
399
- let priceWei = '0';
400
- try {
401
- const priceKey = encodeString(METADATA_KEYS.PRICE_WEI);
402
- const priceData = SELECTORS.getMetadata + agentIdHex + '0000000000000000000000000000000000000000000000000000000000000040' + priceKey;
403
- const priceResult = await ethCall(rpcUrl, IDENTITY_REGISTRY, priceData);
404
- // bytes response: offset (32 bytes) + length (32 bytes) + data
405
- // priceWei is stored as 32-byte bigint
406
- if (priceResult && priceResult.length >= 194) {
407
- const dataLen = parseInt(priceResult.slice(66, 130), 16);
408
- if (dataLen === 32) {
409
- // Extract the 32-byte bigint from data section
410
- const priceHex = priceResult.slice(130, 194);
411
- priceWei = decodeBigInt('0x' + priceHex).toString();
412
- }
413
- }
414
- } catch {
415
- console.log('[getAgentFromContract] No priceWei metadata');
416
- }
417
-
418
- // Get metadata - flaunchToken (optional)
419
- let flaunchToken: string | undefined;
420
- try {
421
- const flaunchKey = encodeString(METADATA_KEYS.FLAUNCH_TOKEN);
422
- const flaunchData = SELECTORS.getMetadata + agentIdHex + '0000000000000000000000000000000000000000000000000000000000000040' + flaunchKey;
423
- const flaunchResult = await ethCall(rpcUrl, IDENTITY_REGISTRY, flaunchData);
424
- // bytes response: offset (32 bytes) + length (32 bytes) + data (padded)
425
- // For address: length=20, data starts at position 130 (after 0x + 64 + 64)
426
- if (flaunchResult && flaunchResult.length >= 194) {
427
- const dataLen = parseInt(flaunchResult.slice(66, 130), 16);
428
- if (dataLen === 20) {
429
- // It's an address - read first 40 hex chars of data (not last!)
430
- const tokenAddr = '0x' + flaunchResult.slice(130, 170).toLowerCase();
431
- if (tokenAddr !== '0x0000000000000000000000000000000000000000') {
432
- flaunchToken = tokenAddr;
433
- }
434
- }
435
- }
436
- } catch (e) {
437
- console.log('[getAgentFromContract] No flaunchToken metadata:', e);
438
- }
439
-
440
- // Get reputation from Reputation Registry
441
- // Step 1: getClients(agentId) to get addresses who left feedback
442
- // Step 2: getSummary(agentId, clients, '', '') with those addresses
443
- let reputation = { count: 0, summaryValue: 0, summaryValueDecimals: 0 };
444
- try {
445
- const clientsData = SELECTORS.getClients + agentIdHex;
446
- const clientsResult = await ethCall(rpcUrl, REPUTATION_REGISTRY, clientsData);
447
-
448
- // Decode address[] response: offset (32) + length (32) + addresses (32 each)
449
- let clients: string[] = [];
450
- if (clientsResult && clientsResult.length > 130) {
451
- const arrLen = parseInt(clientsResult.slice(66, 130), 16);
452
- for (let i = 0; i < arrLen; i++) {
453
- const start = 130 + i * 64;
454
- clients.push(clientsResult.slice(start + 24, start + 64)); // last 20 bytes
455
- }
456
- }
457
-
458
- if (clients.length > 0) {
459
- // Build getSummary calldata with client addresses
460
- // Head: agentId + offset_addr[] + offset_tag1 + offset_tag2
461
- const addrArrayOffset = (4 * 32).toString(16).padStart(64, '0'); // 0x80
462
- const addrDataSize = (1 + clients.length) * 32; // length word + address words
463
- const tag1Offset = (4 * 32 + addrDataSize).toString(16).padStart(64, '0');
464
- const tag2Offset = (4 * 32 + addrDataSize + 32).toString(16).padStart(64, '0');
465
-
466
- let summaryData = SELECTORS.getSummary + agentIdHex +
467
- addrArrayOffset + tag1Offset + tag2Offset;
468
-
469
- // Address array: length + each address padded to 32 bytes
470
- summaryData += clients.length.toString(16).padStart(64, '0');
471
- for (const addr of clients) {
472
- summaryData += addr.padStart(64, '0');
473
- }
474
-
475
- // Empty tag1 and tag2
476
- summaryData += '0000000000000000000000000000000000000000000000000000000000000000';
477
- summaryData += '0000000000000000000000000000000000000000000000000000000000000000';
478
-
479
- const repResult = await ethCall(rpcUrl, REPUTATION_REGISTRY, summaryData);
480
- if (repResult && repResult.length >= 194) {
481
- reputation = {
482
- count: parseInt(repResult.slice(2, 66), 16),
483
- summaryValue: parseInt(repResult.slice(66, 130), 16),
484
- summaryValueDecimals: parseInt(repResult.slice(130, 194), 16),
485
- };
486
- }
487
- }
488
- } catch {
489
- // Reputation might not exist yet
490
- }
491
-
492
- // Parse name/description from agentURI
493
- let name = `Agent #${agentId}`;
494
- let description = '';
495
-
496
- if (agentURI.startsWith('data:application/json')) {
497
- try {
498
- let jsonStr: string;
499
- if (agentURI.includes(';base64,')) {
500
- const base64 = agentURI.split(';base64,')[1];
501
- jsonStr = atob(base64);
502
- } else {
503
- jsonStr = decodeURIComponent(agentURI.split(',')[1]);
504
- }
505
- const meta = JSON.parse(jsonStr);
506
- name = meta.name || name;
507
- description = meta.description || '';
508
- } catch {
509
- // Couldn't parse, use defaults
510
- }
511
- }
512
-
513
- return {
514
- id: '0x' + agentId.toString(16),
515
- agentIdBigInt: agentId.toString(),
516
- owner,
517
- agentURI,
518
- agentWallet,
519
- name,
520
- description,
521
- skills,
522
- endpoint,
523
- priceWei,
524
- flaunchToken,
525
- reputation,
526
- };
527
- } catch (err) {
528
- console.error(`Error fetching agent ${agentId}:`, err);
529
- return null;
530
- }
531
- }
532
-
533
- // ETH price cache for USD conversion
534
- let ethPriceCache: { price: number; fetchedAt: number } | null = null;
535
- const ETH_PRICE_CACHE_TTL = 60_000; // 1 minute
536
-
537
- async function getEthPrice(): Promise<number> {
538
- const now = Date.now();
539
- if (ethPriceCache && now - ethPriceCache.fetchedAt < ETH_PRICE_CACHE_TTL) {
540
- return ethPriceCache.price;
541
- }
542
- try {
543
- const res = await fetch('https://api.coingecko.com/api/v3/simple/price?ids=ethereum&vs_currencies=usd');
544
- if (!res.ok) return ethPriceCache?.price ?? 2500;
545
- const data = await res.json() as { ethereum: { usd: number } };
546
- const price = data.ethereum.usd;
547
- ethPriceCache = { price, fetchedAt: now };
548
- return price;
549
- } catch {
550
- return ethPriceCache?.price ?? 2500;
551
- }
552
- }
553
-
554
- // Fetch market data from Flaunch + DexScreener
555
- async function getFlaunchData(tokenAddress: string): Promise<{
556
- marketCapUSD: number;
557
- volume24hUSD: number;
558
- priceChange24h: number;
559
- holders: number;
560
- image: string;
561
- symbol: string;
562
- liquidityUSD: number;
563
- } | null> {
564
- try {
565
- // Fetch from Flaunch (image, symbol, holders), DexScreener (volume, priceChange), and ETH price in parallel
566
- const [detailsRes, holdersRes, dexRes, ethPrice] = await Promise.all([
567
- fetch(`${FLAUNCH_API}/tokens/${tokenAddress}`),
568
- fetch(`${FLAUNCH_API}/tokens/${tokenAddress}/holders?limit=1`),
569
- fetch(`https://api.dexscreener.com/latest/dex/tokens/${tokenAddress}`),
570
- getEthPrice(),
571
- ]);
572
-
573
- if (!detailsRes.ok) return null;
574
-
575
- const details = await detailsRes.json() as {
576
- tokenAddress: string;
577
- symbol: string;
578
- name: string;
579
- image: string;
580
- marketCapETH?: string;
581
- };
582
-
583
- let holders = 0;
584
- if (holdersRes.ok) {
585
- const holdersData = await holdersRes.json() as { totalHolders?: string };
586
- holders = parseInt(holdersData.totalHolders || '0', 10);
587
- }
588
-
589
- // Get volume and price change from DexScreener
590
- let volume24hUSD = 0;
591
- let priceChange24h = 0;
592
- let liquidityUSD = 0;
593
- if (dexRes.ok) {
594
- const dexData = await dexRes.json() as {
595
- pairs?: Array<{
596
- priceChange?: { h24?: number };
597
- volume?: { h24?: number };
598
- liquidity?: { usd?: number };
599
- }>;
600
- };
601
- const pair = dexData.pairs?.[0];
602
- if (pair) {
603
- volume24hUSD = pair.volume?.h24 || 0;
604
- priceChange24h = pair.priceChange?.h24 || 0;
605
- liquidityUSD = pair.liquidity?.usd || 0;
606
- }
607
- }
608
-
609
- // Convert marketCap from ETH to USD
610
- const marketCapETH = Number(details.marketCapETH || '0') / 1e18;
611
- const marketCapUSD = marketCapETH * ethPrice;
612
-
613
- return {
614
- marketCapUSD,
615
- volume24hUSD,
616
- priceChange24h,
617
- holders,
618
- image: details.image || '',
619
- symbol: details.symbol || '',
620
- liquidityUSD,
621
- };
622
- } catch {
623
- return null;
624
- }
625
- }
626
-
627
- // Cache for agents (5 minute TTL)
628
- let agentsCache: { agents: Agent[]; fetchedAt: number } | null = null;
629
- const CACHE_TTL = 5 * 60 * 1000; // 5 minutes
630
-
631
- /**
632
- * Get all Moltlaunch agents (from KV index)
633
- */
634
- export async function getAllAgents(env: Env): Promise<Agent[]> {
635
- const now = Date.now();
636
- if (agentsCache && now - agentsCache.fetchedAt < CACHE_TTL) {
637
- return agentsCache.agents;
638
- }
639
-
640
- const rpcUrl = env.ALCHEMY_RPC || env.BASE_RPC || 'https://mainnet.base.org';
641
-
642
- try {
643
- // Get agent IDs from our KV index
644
- const indexKey = 'moltlaunch:agents';
645
- const indexRaw = await env.TASKS_KV.get(indexKey);
646
- const agentIds: string[] = indexRaw ? JSON.parse(indexRaw) : [];
647
-
648
- console.log(`[getAllAgents] Found ${agentIds.length} agents in KV index`);
649
-
650
- if (agentIds.length === 0) {
651
- return [];
652
- }
653
-
654
- // Fetch details for each agent
655
- const agents: Agent[] = [];
656
-
657
- for (const agentId of agentIds) {
658
- const agent = await getAgentFromContract(rpcUrl, BigInt(agentId));
659
- if (agent) {
660
- // Fetch Flaunch market data and burn stats if available
661
- if (agent.flaunchToken) {
662
- const [flaunchData, totalBurnedETH, totalBurnedTokens, ethPrice] = await Promise.all([
663
- getFlaunchData(agent.flaunchToken),
664
- getTotalBurned(rpcUrl, agent.flaunchToken),
665
- getTotalTokensBurned(rpcUrl, agent.flaunchToken),
666
- getEthPrice(),
667
- ]);
668
- if (flaunchData) {
669
- agent.marketCapUSD = flaunchData.marketCapUSD;
670
- agent.volume24hUSD = flaunchData.volume24hUSD;
671
- agent.priceChange24h = flaunchData.priceChange24h;
672
- agent.liquidityUSD = flaunchData.liquidityUSD;
673
- agent.holders = flaunchData.holders;
674
- agent.image = flaunchData.image;
675
- agent.symbol = flaunchData.symbol;
676
- agent.flaunchUrl = `https://flaunch.gg/base/coin/${agent.flaunchToken}`;
677
- }
678
- if (totalBurnedETH > 0) {
679
- agent.totalBurnedETH = totalBurnedETH;
680
- agent.totalBurnedUSD = totalBurnedETH * ethPrice;
681
- }
682
- if (totalBurnedTokens > 0) {
683
- agent.totalBurnedTokens = totalBurnedTokens;
684
- }
685
- }
686
- agents.push(agent);
687
- }
688
- }
689
-
690
- // Sort by market cap (if available) or agent ID
691
- agents.sort((a, b) => {
692
- if (a.marketCapUSD && b.marketCapUSD) {
693
- return b.marketCapUSD - a.marketCapUSD;
694
- }
695
- if (a.marketCapUSD) return -1;
696
- if (b.marketCapUSD) return 1;
697
- return parseInt(b.agentIdBigInt) - parseInt(a.agentIdBigInt);
698
- });
699
-
700
- agentsCache = { agents, fetchedAt: now };
701
- return agents;
702
- } catch (err) {
703
- console.error('Error fetching agents:', err);
704
- return agentsCache?.agents ?? [];
705
- }
706
- }
707
-
708
- /**
709
- * Get a single agent by ID
710
- */
711
- export async function getAgentById(env: Env, agentId: string): Promise<Agent | null> {
712
- const rpcUrl = env.ALCHEMY_RPC || env.BASE_RPC || 'https://mainnet.base.org';
713
- console.log('[getAgentById] Using RPC:', rpcUrl.substring(0, 50) + '...');
714
- console.log('[getAgentById] Looking for agent:', agentId);
715
-
716
- try {
717
- // Parse agentId (can be hex or decimal)
718
- const id = agentId.startsWith('0x') ? BigInt(agentId) : BigInt(agentId);
719
- console.log('[getAgentById] Parsed ID:', id.toString());
720
-
721
- const agent = await getAgentFromContract(rpcUrl, id);
722
- console.log('[getAgentById] Got agent:', agent ? 'yes' : 'null');
723
-
724
- if (agent && agent.flaunchToken) {
725
- const [flaunchData, totalBurnedETH, totalBurnedTokens, ethPrice] = await Promise.all([
726
- getFlaunchData(agent.flaunchToken),
727
- getTotalBurned(rpcUrl, agent.flaunchToken),
728
- getTotalTokensBurned(rpcUrl, agent.flaunchToken),
729
- getEthPrice(),
730
- ]);
731
- if (flaunchData) {
732
- agent.marketCapUSD = flaunchData.marketCapUSD;
733
- agent.volume24hUSD = flaunchData.volume24hUSD;
734
- agent.priceChange24h = flaunchData.priceChange24h;
735
- agent.liquidityUSD = flaunchData.liquidityUSD;
736
- agent.holders = flaunchData.holders;
737
- agent.image = flaunchData.image;
738
- agent.symbol = flaunchData.symbol;
739
- agent.flaunchUrl = `https://flaunch.gg/base/coin/${agent.flaunchToken}`;
740
- }
741
- if (totalBurnedETH > 0) {
742
- agent.totalBurnedETH = totalBurnedETH;
743
- agent.totalBurnedUSD = totalBurnedETH * ethPrice;
744
- }
745
- if (totalBurnedTokens > 0) {
746
- agent.totalBurnedTokens = totalBurnedTokens;
747
- }
748
- }
749
-
750
- return agent;
751
- } catch (err) {
752
- console.error(`Error fetching agent ${agentId}:`, err);
753
- return null;
754
- }
755
- }