agentlaunch-templates 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.
Files changed (37) hide show
  1. package/dist/generator.d.ts +43 -0
  2. package/dist/generator.d.ts.map +1 -0
  3. package/dist/generator.js +213 -0
  4. package/dist/generator.js.map +1 -0
  5. package/dist/index.d.ts +41 -0
  6. package/dist/index.d.ts.map +1 -0
  7. package/dist/index.js +39 -0
  8. package/dist/index.js.map +1 -0
  9. package/dist/registry.d.ts +49 -0
  10. package/dist/registry.d.ts.map +1 -0
  11. package/dist/registry.js +47 -0
  12. package/dist/registry.js.map +1 -0
  13. package/dist/templates/custom.d.ts +11 -0
  14. package/dist/templates/custom.d.ts.map +1 -0
  15. package/dist/templates/custom.js +458 -0
  16. package/dist/templates/custom.js.map +1 -0
  17. package/dist/templates/data-analyzer.d.ts +11 -0
  18. package/dist/templates/data-analyzer.d.ts.map +1 -0
  19. package/dist/templates/data-analyzer.js +565 -0
  20. package/dist/templates/data-analyzer.js.map +1 -0
  21. package/dist/templates/gifter.d.ts +15 -0
  22. package/dist/templates/gifter.d.ts.map +1 -0
  23. package/dist/templates/gifter.js +717 -0
  24. package/dist/templates/gifter.js.map +1 -0
  25. package/dist/templates/price-monitor.d.ts +11 -0
  26. package/dist/templates/price-monitor.d.ts.map +1 -0
  27. package/dist/templates/price-monitor.js +577 -0
  28. package/dist/templates/price-monitor.js.map +1 -0
  29. package/dist/templates/research.d.ts +11 -0
  30. package/dist/templates/research.d.ts.map +1 -0
  31. package/dist/templates/research.js +593 -0
  32. package/dist/templates/research.js.map +1 -0
  33. package/dist/templates/trading-bot.d.ts +11 -0
  34. package/dist/templates/trading-bot.d.ts.map +1 -0
  35. package/dist/templates/trading-bot.js +559 -0
  36. package/dist/templates/trading-bot.js.map +1 -0
  37. package/package.json +24 -0
@@ -0,0 +1 @@
1
+ {"version":3,"file":"gifter.js","sourceRoot":"","sources":["../../src/templates/gifter.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;GAWG;AAIH,MAAM,CAAC,MAAM,QAAQ,GAAkB;IACrC,IAAI,EAAE,QAAQ;IACd,WAAW,EACT,4EAA4E;IAC9E,QAAQ,EAAE,gBAAgB;IAC1B,SAAS,EAAE;QACT,EAAE,IAAI,EAAE,YAAY,EAAE,QAAQ,EAAE,IAAI,EAAE,WAAW,EAAE,mBAAmB,EAAE;QACxE;YACE,IAAI,EAAE,aAAa;YACnB,OAAO,EAAE,mDAAmD;YAC5D,WAAW,EAAE,2CAA2C;SACzD;QACD;YACE,IAAI,EAAE,sBAAsB;YAC5B,OAAO,EAAE,KAAK;YACd,WAAW,EAAE,sCAAsC;SACpD;QACD;YACE,IAAI,EAAE,sBAAsB;YAC5B,OAAO,EAAE,MAAM;YACf,WAAW,EAAE,sCAAsC;SACpD;QACD;YACE,IAAI,EAAE,kBAAkB;YACxB,QAAQ,EAAE,IAAI;YACd,WAAW,EAAE,0CAA0C;SACxD;QACD;YACE,IAAI,EAAE,uBAAuB;YAC7B,OAAO,EAAE,GAAG;YACZ,WAAW,EAAE,uCAAuC;SACrD;QACD;YACE,IAAI,EAAE,uBAAuB;YAC7B,OAAO,EAAE,GAAG;YACZ,WAAW,EAAE,6CAA6C;SAC3D;QACD;YACE,IAAI,EAAE,yBAAyB;YAC/B,OAAO,EAAE,MAAM;YACf,WAAW,EAAE,6DAA6D;SAC3E;KACF;IACD,YAAY,EAAE,CAAC,UAAU,EAAE,MAAM,CAAC;IAClC,OAAO,EAAE;QACP,oBAAoB;QACpB,qBAAqB;QACrB,eAAe;QACf,qBAAqB;QACrB,sBAAsB;QACtB,iBAAiB;QACjB,iBAAiB;KAClB;IACD,IAAI,EAAE;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;CA0oBP;CACA,CAAC"}
@@ -0,0 +1,11 @@
1
+ /**
2
+ * price-monitor.ts — Monitors token prices and sends alerts when thresholds are crossed
3
+ *
4
+ * Platform constants (source of truth: deployed smart contracts):
5
+ * - Deploy fee: 120 FET (read dynamically, can change via multi-sig)
6
+ * - Graduation target: 30,000 FET -> auto DEX listing
7
+ * - Trading fee: 2% -> 100% to protocol treasury (NO creator fee)
8
+ */
9
+ import type { AgentTemplate } from "../registry.js";
10
+ export declare const template: AgentTemplate;
11
+ //# sourceMappingURL=price-monitor.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"price-monitor.d.ts","sourceRoot":"","sources":["../../src/templates/price-monitor.ts"],"names":[],"mappings":"AAAA;;;;;;;GAOG;AAEH,OAAO,KAAK,EAAE,aAAa,EAAE,MAAM,gBAAgB,CAAC;AAEpD,eAAO,MAAM,QAAQ,EAAE,aAwjBtB,CAAC"}
@@ -0,0 +1,577 @@
1
+ /**
2
+ * price-monitor.ts — Monitors token prices and sends alerts when thresholds are crossed
3
+ *
4
+ * Platform constants (source of truth: deployed smart contracts):
5
+ * - Deploy fee: 120 FET (read dynamically, can change via multi-sig)
6
+ * - Graduation target: 30,000 FET -> auto DEX listing
7
+ * - Trading fee: 2% -> 100% to protocol treasury (NO creator fee)
8
+ */
9
+ export const template = {
10
+ name: "price-monitor",
11
+ description: "Monitors token prices and sends alerts when thresholds are crossed",
12
+ category: "AI/ML",
13
+ variables: [
14
+ { name: "agent_name", required: true, description: "Name of the agent" },
15
+ {
16
+ name: "description",
17
+ default: "Monitors token prices and sends alerts when thresholds are crossed",
18
+ description: "Short description of what this agent does",
19
+ },
20
+ {
21
+ name: "token_address",
22
+ required: false,
23
+ description: "Default token address to monitor (can be overridden per user)",
24
+ },
25
+ {
26
+ name: "alert_threshold",
27
+ default: "5",
28
+ description: "Price change percentage that triggers an alert",
29
+ },
30
+ {
31
+ name: "poll_interval_seconds",
32
+ default: "60",
33
+ description: "How often to poll prices (seconds)",
34
+ },
35
+ {
36
+ name: "rate_limit_per_minute",
37
+ default: "20",
38
+ description: "Max requests per user per minute",
39
+ },
40
+ {
41
+ name: "free_requests_per_day",
42
+ default: "10",
43
+ description: "Free tier daily request quota",
44
+ },
45
+ {
46
+ name: "premium_token_threshold",
47
+ default: "1000",
48
+ description: "Token balance required for premium tier",
49
+ },
50
+ ],
51
+ dependencies: ["requests"],
52
+ secrets: ["AGENTVERSE_API_KEY", "AGENTLAUNCH_API_KEY", "AGENT_ADDRESS", "AGENT_OWNER_ADDRESS"],
53
+ code: `#!/usr/bin/env python3
54
+ """
55
+ {{agent_name}} — AgentLaunch Price Monitor Agent
56
+ Generated by: agentlaunch scaffold {{agent_name}} --type price-monitor
57
+
58
+ Watches token prices on agent-launch.ai and sends alerts when the price
59
+ moves by more than {{alert_threshold}}% within the polling window.
60
+
61
+ Platform constants (source of truth: deployed smart contracts):
62
+ - Deploy fee: 120 FET (read dynamically, can change via multi-sig)
63
+ - Graduation target: 30,000 FET -> auto DEX listing
64
+ - Trading fee: 2% -> 100% to protocol treasury (NO creator fee)
65
+ """
66
+
67
+ from uagents import Agent, Context, Protocol
68
+ from uagents_core.contrib.protocols.chat import (
69
+ ChatAcknowledgement,
70
+ ChatMessage,
71
+ EndSessionContent,
72
+ TextContent,
73
+ chat_protocol_spec,
74
+ )
75
+
76
+ import json
77
+ import os
78
+ import time
79
+ from collections import defaultdict
80
+ from datetime import datetime
81
+ from typing import Any, Dict, List, Optional
82
+ from uuid import uuid4
83
+
84
+ import requests
85
+
86
+ # ==============================================================================
87
+ # API CONFIG — Override via environment variables, never hardcode
88
+ # ==============================================================================
89
+
90
+ AGENTLAUNCH_API = os.environ.get("AGENTLAUNCH_API", "https://agent-launch.ai/api")
91
+
92
+ # ==============================================================================
93
+ # BUSINESS CONFIG
94
+ # ==============================================================================
95
+
96
+ OWNER_ADDRESS = os.environ.get("AGENT_OWNER_ADDRESS", "")
97
+
98
+ BUSINESS = {
99
+ "name": "{{agent_name}}",
100
+ "description": "{{description}}",
101
+ "version": "1.0.0",
102
+ "default_token_address": "{{token_address}}",
103
+ "alert_threshold_pct": float("{{alert_threshold}}"),
104
+ "poll_interval_seconds": int("{{poll_interval_seconds}}"),
105
+ "free_requests_per_day": {{free_requests_per_day}},
106
+ "premium_token_threshold": {{premium_token_threshold}},
107
+ "rate_limit_per_minute": {{rate_limit_per_minute}},
108
+ "max_input_length": 5000,
109
+ }
110
+
111
+
112
+ # ==============================================================================
113
+ # LAYER 1: FOUNDATION
114
+ # ==============================================================================
115
+
116
+
117
+ class Logger:
118
+ """Structured logging with audit trail."""
119
+
120
+ @staticmethod
121
+ def info(ctx: Context, event: str, data: Optional[Dict] = None) -> None:
122
+ ctx.logger.info(f"[{event}] {json.dumps(data or {})}")
123
+
124
+ @staticmethod
125
+ def audit(ctx: Context, user: str, action: str) -> None:
126
+ ctx.logger.info(
127
+ f"[AUDIT] user={user[:20]} action={action} "
128
+ f"ts={datetime.utcnow().isoformat()}"
129
+ )
130
+
131
+ @staticmethod
132
+ def error(ctx: Context, event: str, error: str) -> None:
133
+ ctx.logger.error(f"[{event}] {error}")
134
+
135
+
136
+ # ==============================================================================
137
+ # LAYER 2: SECURITY
138
+ # ==============================================================================
139
+
140
+
141
+ class Security:
142
+ """Rate limiting and input validation."""
143
+
144
+ def __init__(self) -> None:
145
+ self._requests: Dict[str, List[float]] = defaultdict(list)
146
+ self._check_count: int = 0
147
+
148
+ def check(self, ctx: Context, user_id: str, message: str) -> tuple:
149
+ now = time.time()
150
+
151
+ self._requests[user_id] = [
152
+ t for t in self._requests[user_id] if now - t < 60
153
+ ]
154
+ if len(self._requests[user_id]) >= BUSINESS["rate_limit_per_minute"]:
155
+ return None, "Rate limit exceeded. Please wait a moment."
156
+ self._requests[user_id].append(now)
157
+
158
+ self._check_count += 1
159
+ if self._check_count % 100 == 0:
160
+ stale = [
161
+ k
162
+ for k, v in self._requests.items()
163
+ if not v or (now - max(v)) > 300
164
+ ]
165
+ for k in stale:
166
+ del self._requests[k]
167
+
168
+ if not message or not message.strip():
169
+ return None, "Empty message."
170
+ if len(message) > BUSINESS["max_input_length"]:
171
+ return None, f"Message too long (max {BUSINESS['max_input_length']} chars)."
172
+
173
+ return message.strip(), None
174
+
175
+
176
+ # ==============================================================================
177
+ # LAYER 3: STABILITY
178
+ # ==============================================================================
179
+
180
+
181
+ class Health:
182
+ """Track uptime and error rate."""
183
+
184
+ def __init__(self) -> None:
185
+ self._start: datetime = datetime.utcnow()
186
+ self._requests: int = 0
187
+ self._errors: int = 0
188
+
189
+ def record(self, success: bool) -> None:
190
+ self._requests += 1
191
+ if not success:
192
+ self._errors += 1
193
+
194
+ def status(self) -> Dict[str, Any]:
195
+ uptime = (datetime.utcnow() - self._start).total_seconds()
196
+ error_rate = (self._errors / self._requests * 100) if self._requests else 0
197
+ return {
198
+ "status": "healthy" if error_rate < 10 else "degraded",
199
+ "uptime_seconds": int(uptime),
200
+ "requests": self._requests,
201
+ "error_rate": f"{error_rate:.1f}%",
202
+ }
203
+
204
+
205
+ # ==============================================================================
206
+ # LAYER 4: SPEED
207
+ # ==============================================================================
208
+
209
+
210
+ class Cache:
211
+ """In-memory TTL cache."""
212
+
213
+ def __init__(self, max_size: int = 1000) -> None:
214
+ self._data: Dict[str, tuple] = {}
215
+ self._max_size: int = max_size
216
+
217
+ def get(self, key: str) -> Any:
218
+ if key in self._data:
219
+ value, expires = self._data[key]
220
+ if expires > time.time():
221
+ return value
222
+ del self._data[key]
223
+ return None
224
+
225
+ def set(self, key: str, value: Any, ttl: int = 300) -> None:
226
+ if len(self._data) >= self._max_size:
227
+ now = time.time()
228
+ expired = [k for k, (_, exp) in self._data.items() if exp <= now]
229
+ for k in expired:
230
+ del self._data[k]
231
+ if len(self._data) >= self._max_size:
232
+ to_drop = sorted(self._data.items(), key=lambda x: x[1][1])[
233
+ : self._max_size // 10
234
+ ]
235
+ for k, _ in to_drop:
236
+ del self._data[k]
237
+ self._data[key] = (value, time.time() + ttl)
238
+
239
+
240
+ # ==============================================================================
241
+ # LAYER 5: REVENUE
242
+ # ==============================================================================
243
+
244
+
245
+ class Revenue:
246
+ """Token-gated access and daily usage quotas."""
247
+
248
+ def __init__(self, cache: Cache) -> None:
249
+ self._cache = cache
250
+ self._usage: Dict[str, List[str]] = defaultdict(list)
251
+
252
+ def get_tier(self, user_address: str) -> str:
253
+ cached = self._cache.get(f"tier:{user_address}")
254
+ if cached is not None:
255
+ return cached
256
+ try:
257
+ r = requests.get(
258
+ f"{AGENTLAUNCH_API}/agents/token/{user_address}", timeout=5
259
+ )
260
+ if r.status_code == 200:
261
+ data = r.json()
262
+ balance = data.get("balance", 0)
263
+ tier = (
264
+ "premium"
265
+ if balance >= BUSINESS["premium_token_threshold"]
266
+ else "free"
267
+ )
268
+ self._cache.set(f"tier:{user_address}", tier, ttl=300)
269
+ return tier
270
+ except Exception:
271
+ pass
272
+ return "free"
273
+
274
+ def check_quota(self, user_id: str, tier: str) -> tuple:
275
+ today = datetime.utcnow().date().isoformat()
276
+ self._usage[user_id] = [
277
+ t for t in self._usage[user_id] if t.startswith(today)
278
+ ]
279
+ today_usage = len(self._usage[user_id])
280
+ limit = 1000 if tier == "premium" else BUSINESS["free_requests_per_day"]
281
+ if today_usage >= limit:
282
+ if tier == "free":
283
+ return False, (
284
+ f"Free limit reached ({limit}/day). "
285
+ f"Hold {BUSINESS['premium_token_threshold']} tokens for premium!"
286
+ )
287
+ return False, f"Daily limit reached ({limit}/day)."
288
+ self._usage[user_id].append(datetime.utcnow().isoformat())
289
+ return True, None
290
+
291
+
292
+ # ==============================================================================
293
+ # AGENTLAUNCH INTEGRATION
294
+ # ==============================================================================
295
+
296
+
297
+ class AgentLaunch:
298
+ """Create and manage tokens on AgentLaunch."""
299
+
300
+ @staticmethod
301
+ def tokenize() -> Dict:
302
+ agent_address = os.environ.get("AGENT_ADDRESS")
303
+ if not agent_address:
304
+ return {"error": "AGENT_ADDRESS env var not set."}
305
+ try:
306
+ r = requests.post(
307
+ f"{AGENTLAUNCH_API}/agents/tokenize",
308
+ headers={
309
+ "X-API-Key": os.environ.get("AGENTLAUNCH_API_KEY", ""),
310
+ "Content-Type": "application/json",
311
+ },
312
+ json={
313
+ "agentAddress": agent_address,
314
+ "name": BUSINESS["name"],
315
+ "description": BUSINESS["description"],
316
+ },
317
+ timeout=30,
318
+ )
319
+ return r.json() if r.status_code in [200, 201] else {"error": r.text}
320
+ except Exception as e:
321
+ return {"error": str(e)}
322
+
323
+
324
+ # ==============================================================================
325
+ # PRICE MONITOR BUSINESS LOGIC
326
+ # ==============================================================================
327
+
328
+
329
+ class PriceMonitorBusiness:
330
+ """
331
+ Fetches token price from agent-launch.ai/api, compares with last known
332
+ price, and reports whether the threshold has been breached.
333
+
334
+ Note: Trading fee is 2% -> 100% to protocol treasury (no creator fee).
335
+ """
336
+
337
+ def __init__(self) -> None:
338
+ self._price_history: Dict[str, List[Dict]] = defaultdict(list)
339
+
340
+ def fetch_price(self, token_address: str) -> Optional[float]:
341
+ """Fetch the current token price from the AgentLaunch API."""
342
+ try:
343
+ r = requests.get(
344
+ f"{AGENTLAUNCH_API}/agents/token/{token_address}", timeout=10
345
+ )
346
+ if r.status_code == 200:
347
+ data = r.json()
348
+ # Price may be in data.price or data.data.price depending on API version
349
+ price = (
350
+ data.get("price")
351
+ or (data.get("data") or {}).get("price")
352
+ )
353
+ return float(price) if price is not None else None
354
+ except Exception:
355
+ pass
356
+ return None
357
+
358
+ def check_alert(self, token_address: str, current_price: float) -> Optional[str]:
359
+ """Compare current price to recent history; return alert string if threshold crossed."""
360
+ history = self._price_history[token_address]
361
+ if not history:
362
+ return None
363
+ baseline = history[0]["price"]
364
+ if baseline == 0:
365
+ return None
366
+ change_pct = ((current_price - baseline) / baseline) * 100
367
+ threshold = BUSINESS["alert_threshold_pct"]
368
+ if abs(change_pct) >= threshold:
369
+ direction = "UP" if change_pct > 0 else "DOWN"
370
+ return (
371
+ f"ALERT [{token_address[:10]}...] price {direction} "
372
+ f"{change_pct:+.2f}% (baseline: {baseline:.6f} FET, "
373
+ f"current: {current_price:.6f} FET)"
374
+ )
375
+ return None
376
+
377
+ def record_price(self, token_address: str, price: float) -> None:
378
+ history = self._price_history[token_address]
379
+ history.append({"price": price, "ts": datetime.utcnow().isoformat()})
380
+ # Keep last 100 data points
381
+ if len(history) > 100:
382
+ self._price_history[token_address] = history[-100:]
383
+
384
+ async def handle(self, ctx: Context, user_id: str, message: str, tier: str) -> str:
385
+ lower = message.lower()
386
+
387
+ # Parse "watch <address>" or "monitor <address>"
388
+ if lower.startswith("watch ") or lower.startswith("monitor "):
389
+ parts = message.split()
390
+ if len(parts) >= 2:
391
+ token_address = parts[1].strip()
392
+ price = self.fetch_price(token_address)
393
+ if price is None:
394
+ return f"Could not fetch price for {token_address}. Check the address."
395
+ self.record_price(token_address, price)
396
+ return (
397
+ f"Now monitoring {token_address[:12]}...\\n"
398
+ f"Current price: {price:.6f} FET\\n"
399
+ f"Alert threshold: {BUSINESS['alert_threshold_pct']}% change"
400
+ )
401
+
402
+ # "check <address>" — check current price and alert status
403
+ if lower.startswith("check "):
404
+ parts = message.split()
405
+ if len(parts) >= 2:
406
+ token_address = parts[1].strip()
407
+ price = self.fetch_price(token_address)
408
+ if price is None:
409
+ return f"Could not fetch price for {token_address}."
410
+ self.record_price(token_address, price)
411
+ alert = self.check_alert(token_address, price)
412
+ status = alert if alert else f"No alert. Price: {price:.6f} FET"
413
+ return status
414
+
415
+ # "price <address>" — quick price lookup
416
+ if lower.startswith("price "):
417
+ parts = message.split()
418
+ if len(parts) >= 2:
419
+ token_address = parts[1].strip()
420
+ price = self.fetch_price(token_address)
421
+ if price is None:
422
+ return f"Could not fetch price for {token_address}."
423
+ return f"Price of {token_address[:12]}...: {price:.6f} FET"
424
+
425
+ # Default: check the configured default token
426
+ default_addr = BUSINESS["default_token_address"]
427
+ if default_addr:
428
+ price = self.fetch_price(default_addr)
429
+ if price is not None:
430
+ self.record_price(default_addr, price)
431
+ alert = self.check_alert(default_addr, price)
432
+ info = alert if alert else f"Price: {price:.6f} FET (no alert)"
433
+ return (
434
+ f"Default token ({default_addr[:12]}...) — {info}\\n\\n"
435
+ f"Commands: watch <addr>, check <addr>, price <addr>"
436
+ )
437
+
438
+ return (
439
+ f"Welcome to {BUSINESS['name']}!\\n\\n"
440
+ f"Commands:\\n"
441
+ f" watch <token_address> — start monitoring a token\\n"
442
+ f" check <token_address> — check current price + alert status\\n"
443
+ f" price <token_address> — quick price lookup\\n\\n"
444
+ f"Alert threshold: {BUSINESS['alert_threshold_pct']}% price change"
445
+ )
446
+
447
+
448
+ # ==============================================================================
449
+ # REPLY HELPER
450
+ # ==============================================================================
451
+
452
+
453
+ async def reply(ctx: Context, sender: str, text: str, end: bool = False) -> None:
454
+ content = [TextContent(type="text", text=text)]
455
+ if end:
456
+ content.append(EndSessionContent(type="end-session"))
457
+ try:
458
+ await ctx.send(
459
+ sender,
460
+ ChatMessage(timestamp=datetime.utcnow(), msg_id=uuid4(), content=content),
461
+ )
462
+ except Exception as e:
463
+ ctx.logger.error(f"Failed to send reply to {sender[:20]}: {e}")
464
+
465
+
466
+ # ==============================================================================
467
+ # MAIN AGENT
468
+ # ==============================================================================
469
+
470
+ cache = Cache(max_size=1000)
471
+ security = Security()
472
+ health = Health()
473
+ revenue = Revenue(cache)
474
+ business = PriceMonitorBusiness()
475
+
476
+ agent = Agent()
477
+ chat_proto = Protocol(spec=chat_protocol_spec)
478
+
479
+
480
+ @chat_proto.on_message(ChatMessage)
481
+ async def handle_chat(ctx: Context, sender: str, msg: ChatMessage) -> None:
482
+ try:
483
+ await ctx.send(
484
+ sender,
485
+ ChatAcknowledgement(
486
+ timestamp=datetime.utcnow(), acknowledged_msg_id=msg.msg_id
487
+ ),
488
+ )
489
+ except Exception as e:
490
+ ctx.logger.error(f"Failed to send ack to {sender[:20]}: {e}")
491
+
492
+ text = " ".join(
493
+ item.text for item in msg.content if isinstance(item, TextContent)
494
+ ).strip()
495
+ text = text[: BUSINESS["max_input_length"]]
496
+
497
+ clean, error = security.check(ctx, sender, text)
498
+ if error:
499
+ health.record(False)
500
+ await reply(ctx, sender, error, end=True)
501
+ return
502
+
503
+ Logger.audit(ctx, sender, "request")
504
+
505
+ lower = clean.lower()
506
+
507
+ if lower in ("help", "?"):
508
+ tier = revenue.get_tier(sender)
509
+ await reply(
510
+ ctx,
511
+ sender,
512
+ f"**{BUSINESS['name']}** v{BUSINESS['version']}\\n\\n"
513
+ f"{BUSINESS['description']}\\n\\n"
514
+ f"Your tier: {tier.upper()}\\n\\n"
515
+ f"Commands: help, status, tokenize, watch <addr>, check <addr>, price <addr>",
516
+ )
517
+ return
518
+
519
+ if lower == "status":
520
+ s = health.status()
521
+ await reply(
522
+ ctx,
523
+ sender,
524
+ f"Status: {s['status']} | Uptime: {s['uptime_seconds']}s | "
525
+ f"Requests: {s['requests']} | Error rate: {s['error_rate']}",
526
+ )
527
+ return
528
+
529
+ if "tokenize" in lower:
530
+ if OWNER_ADDRESS and sender != OWNER_ADDRESS:
531
+ await reply(ctx, sender, "Only the agent owner can trigger tokenization.", end=True)
532
+ return
533
+ result = AgentLaunch.tokenize()
534
+ link = result.get("data", {}).get("handoff_link") or result.get("handoff_link")
535
+ await reply(
536
+ ctx,
537
+ sender,
538
+ f"Token created! Deploy here: {link}" if link else f"Result: {json.dumps(result)}",
539
+ end=True,
540
+ )
541
+ return
542
+
543
+ tier = revenue.get_tier(sender)
544
+ allowed, quota_error = revenue.check_quota(sender, tier)
545
+ if not allowed:
546
+ health.record(False)
547
+ await reply(ctx, sender, quota_error, end=True)
548
+ return
549
+
550
+ try:
551
+ response = await business.handle(ctx, sender, clean, tier)
552
+ health.record(True)
553
+ except Exception as e:
554
+ health.record(False)
555
+ Logger.error(ctx, "business_handle", str(e))
556
+ response = "Something went wrong. Please try again."
557
+
558
+ await reply(ctx, sender, response, end=True)
559
+
560
+
561
+ @chat_proto.on_message(ChatAcknowledgement)
562
+ async def handle_ack(ctx: Context, sender: str, msg: ChatAcknowledgement) -> None:
563
+ ctx.logger.debug(f"Ack from {sender[:20]} for msg {msg.acknowledged_msg_id}")
564
+
565
+
566
+ @agent.on_interval(period=3600)
567
+ async def periodic_health(ctx: Context) -> None:
568
+ ctx.logger.info(f"[HEALTH] {json.dumps(health.status())}")
569
+
570
+
571
+ agent.include(chat_proto, publish_manifest=True)
572
+
573
+ if __name__ == "__main__":
574
+ agent.run()
575
+ `,
576
+ };
577
+ //# sourceMappingURL=price-monitor.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"price-monitor.js","sourceRoot":"","sources":["../../src/templates/price-monitor.ts"],"names":[],"mappings":"AAAA;;;;;;;GAOG;AAIH,MAAM,CAAC,MAAM,QAAQ,GAAkB;IACrC,IAAI,EAAE,eAAe;IACrB,WAAW,EACT,oEAAoE;IACtE,QAAQ,EAAE,OAAO;IACjB,SAAS,EAAE;QACT,EAAE,IAAI,EAAE,YAAY,EAAE,QAAQ,EAAE,IAAI,EAAE,WAAW,EAAE,mBAAmB,EAAE;QACxE;YACE,IAAI,EAAE,aAAa;YACnB,OAAO,EAAE,oEAAoE;YAC7E,WAAW,EAAE,2CAA2C;SACzD;QACD;YACE,IAAI,EAAE,eAAe;YACrB,QAAQ,EAAE,KAAK;YACf,WAAW,EAAE,+DAA+D;SAC7E;QACD;YACE,IAAI,EAAE,iBAAiB;YACvB,OAAO,EAAE,GAAG;YACZ,WAAW,EAAE,gDAAgD;SAC9D;QACD;YACE,IAAI,EAAE,uBAAuB;YAC7B,OAAO,EAAE,IAAI;YACb,WAAW,EAAE,oCAAoC;SAClD;QACD;YACE,IAAI,EAAE,uBAAuB;YAC7B,OAAO,EAAE,IAAI;YACb,WAAW,EAAE,kCAAkC;SAChD;QACD;YACE,IAAI,EAAE,uBAAuB;YAC7B,OAAO,EAAE,IAAI;YACb,WAAW,EAAE,+BAA+B;SAC7C;QACD;YACE,IAAI,EAAE,yBAAyB;YAC/B,OAAO,EAAE,MAAM;YACf,WAAW,EAAE,yCAAyC;SACvD;KACF;IACD,YAAY,EAAE,CAAC,UAAU,CAAC;IAC1B,OAAO,EAAE,CAAC,oBAAoB,EAAE,qBAAqB,EAAE,eAAe,EAAE,qBAAqB,CAAC;IAC9F,IAAI,EAAE;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;CA0gBP;CACA,CAAC"}
@@ -0,0 +1,11 @@
1
+ /**
2
+ * research.ts — Delivers on-demand research reports for AgentLaunch tokens
3
+ *
4
+ * Platform constants (source of truth: deployed smart contracts):
5
+ * - Deploy fee: 120 FET (read dynamically, can change via multi-sig)
6
+ * - Graduation target: 30,000 FET -> auto DEX listing
7
+ * - Trading fee: 2% -> 100% to protocol treasury (NO creator fee)
8
+ */
9
+ import type { AgentTemplate } from "../registry.js";
10
+ export declare const template: AgentTemplate;
11
+ //# sourceMappingURL=research.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"research.d.ts","sourceRoot":"","sources":["../../src/templates/research.ts"],"names":[],"mappings":"AAAA;;;;;;;GAOG;AAEH,OAAO,KAAK,EAAE,aAAa,EAAE,MAAM,gBAAgB,CAAC;AAEpD,eAAO,MAAM,QAAQ,EAAE,aAwkBtB,CAAC"}