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.
- package/dist/generator.d.ts +43 -0
- package/dist/generator.d.ts.map +1 -0
- package/dist/generator.js +213 -0
- package/dist/generator.js.map +1 -0
- package/dist/index.d.ts +41 -0
- package/dist/index.d.ts.map +1 -0
- package/dist/index.js +39 -0
- package/dist/index.js.map +1 -0
- package/dist/registry.d.ts +49 -0
- package/dist/registry.d.ts.map +1 -0
- package/dist/registry.js +47 -0
- package/dist/registry.js.map +1 -0
- package/dist/templates/custom.d.ts +11 -0
- package/dist/templates/custom.d.ts.map +1 -0
- package/dist/templates/custom.js +458 -0
- package/dist/templates/custom.js.map +1 -0
- package/dist/templates/data-analyzer.d.ts +11 -0
- package/dist/templates/data-analyzer.d.ts.map +1 -0
- package/dist/templates/data-analyzer.js +565 -0
- package/dist/templates/data-analyzer.js.map +1 -0
- package/dist/templates/gifter.d.ts +15 -0
- package/dist/templates/gifter.d.ts.map +1 -0
- package/dist/templates/gifter.js +717 -0
- package/dist/templates/gifter.js.map +1 -0
- package/dist/templates/price-monitor.d.ts +11 -0
- package/dist/templates/price-monitor.d.ts.map +1 -0
- package/dist/templates/price-monitor.js +577 -0
- package/dist/templates/price-monitor.js.map +1 -0
- package/dist/templates/research.d.ts +11 -0
- package/dist/templates/research.d.ts.map +1 -0
- package/dist/templates/research.js +593 -0
- package/dist/templates/research.js.map +1 -0
- package/dist/templates/trading-bot.d.ts +11 -0
- package/dist/templates/trading-bot.d.ts.map +1 -0
- package/dist/templates/trading-bot.js +559 -0
- package/dist/templates/trading-bot.js.map +1 -0
- 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"}
|