@intentsolutionsio/token-launch-tracker 1.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 (27) hide show
  1. package/.claude-plugin/plugin.json +22 -0
  2. package/LICENSE +21 -0
  3. package/README.md +162 -0
  4. package/agents/launch-tracker-agent.md +338 -0
  5. package/package.json +43 -0
  6. package/skills/skill-adapter/assets/README.md +5 -0
  7. package/skills/skill-adapter/assets/config-template.json +32 -0
  8. package/skills/skill-adapter/assets/skill-schema.json +28 -0
  9. package/skills/skill-adapter/assets/test-data.json +27 -0
  10. package/skills/skill-adapter/references/README.md +4 -0
  11. package/skills/skill-adapter/references/best-practices.md +69 -0
  12. package/skills/skill-adapter/references/examples.md +73 -0
  13. package/skills/skill-adapter/scripts/README.md +8 -0
  14. package/skills/skill-adapter/scripts/helper-template.sh +42 -0
  15. package/skills/skill-adapter/scripts/validation.sh +32 -0
  16. package/skills/tracking-token-launches/ARD.md +183 -0
  17. package/skills/tracking-token-launches/PRD.md +66 -0
  18. package/skills/tracking-token-launches/SKILL.md +161 -0
  19. package/skills/tracking-token-launches/config/settings.yaml +166 -0
  20. package/skills/tracking-token-launches/references/errors.md +167 -0
  21. package/skills/tracking-token-launches/references/examples.md +292 -0
  22. package/skills/tracking-token-launches/references/implementation.md +36 -0
  23. package/skills/tracking-token-launches/scripts/dex_sources.py +270 -0
  24. package/skills/tracking-token-launches/scripts/event_monitor.py +345 -0
  25. package/skills/tracking-token-launches/scripts/formatters.py +279 -0
  26. package/skills/tracking-token-launches/scripts/launch_tracker.py +572 -0
  27. package/skills/tracking-token-launches/scripts/token_analyzer.py +417 -0
@@ -0,0 +1,279 @@
1
+ #!/usr/bin/env python3
2
+ """
3
+ Token Launch Formatters
4
+
5
+ Format launch data for various outputs.
6
+
7
+ Author: Jeremy Longshore <jeremy@intentsolutions.io>
8
+ Version: 1.0.0
9
+ License: MIT
10
+ """
11
+
12
+ import json
13
+ from datetime import datetime
14
+ from typing import List, Dict, Any
15
+
16
+
17
+ def format_timestamp(ts: int) -> str:
18
+ """Format Unix timestamp."""
19
+ dt = datetime.fromtimestamp(ts)
20
+ return dt.strftime("%Y-%m-%d %H:%M")
21
+
22
+
23
+ def format_age(ts: int) -> str:
24
+ """Format time ago."""
25
+ now = int(datetime.now().timestamp())
26
+ diff = now - ts
27
+
28
+ if diff < 60:
29
+ return f"{diff}s ago"
30
+ elif diff < 3600:
31
+ return f"{diff // 60}m ago"
32
+ elif diff < 86400:
33
+ return f"{diff // 3600}h ago"
34
+ else:
35
+ return f"{diff // 86400}d ago"
36
+
37
+
38
+ def format_address(address: str, length: int = 10) -> str:
39
+ """Format address with ellipsis."""
40
+ if not address:
41
+ return "N/A"
42
+ if len(address) <= length * 2:
43
+ return address
44
+ return f"{address[:length]}...{address[-4:]}"
45
+
46
+
47
+ def format_risk_badge(score: int) -> str:
48
+ """Format risk score as badge."""
49
+ if score >= 70:
50
+ return f"[HIGH RISK: {score}]"
51
+ elif score >= 40:
52
+ return f"[MEDIUM: {score}]"
53
+ elif score >= 20:
54
+ return f"[LOW: {score}]"
55
+ else:
56
+ return f"[OK: {score}]"
57
+
58
+
59
+ def format_supply(supply: int, decimals: int) -> str:
60
+ """Format token supply."""
61
+ value = supply / (10 ** decimals)
62
+
63
+ if value >= 1e12:
64
+ return f"{value / 1e12:.2f}T"
65
+ elif value >= 1e9:
66
+ return f"{value / 1e9:.2f}B"
67
+ elif value >= 1e6:
68
+ return f"{value / 1e6:.2f}M"
69
+ elif value >= 1e3:
70
+ return f"{value / 1e3:.2f}K"
71
+ else:
72
+ return f"{value:.2f}"
73
+
74
+
75
+ def format_new_pairs_table(
76
+ pairs: List[Any],
77
+ token_infos: Dict[str, Any],
78
+ analyses: Dict[str, Any]
79
+ ) -> str:
80
+ """Format new pairs as table.
81
+
82
+ Args:
83
+ pairs: List of PairCreated events
84
+ token_infos: Dict of address -> TokenInfo
85
+ analyses: Dict of address -> ContractAnalysis
86
+
87
+ Returns:
88
+ Formatted table string
89
+ """
90
+ lines = [
91
+ "",
92
+ "NEW TOKEN LAUNCHES",
93
+ "=" * 90,
94
+ f"{'Time':<12} {'Token':<20} {'DEX':<15} {'Chain':<10} {'Risk':<15} {'Pair':<18}",
95
+ "-" * 90,
96
+ ]
97
+
98
+ for pair in pairs:
99
+ # Get new token address (not the base token)
100
+ new_token = pair.token0 # Simplified - should use identify_new_token
101
+
102
+ info = token_infos.get(new_token)
103
+ analysis = analyses.get(new_token)
104
+
105
+ token_str = f"{info.symbol if info else '???'}" if info else "Unknown"
106
+ if len(token_str) > 18:
107
+ token_str = token_str[:15] + "..."
108
+
109
+ risk_str = format_risk_badge(analysis.risk_score) if analysis else "[N/A]"
110
+ pair_str = format_address(pair.pair_address, 8)
111
+ time_str = format_age(pair.timestamp)
112
+
113
+ lines.append(
114
+ f"{time_str:<12} {token_str:<20} {pair.dex:<15} "
115
+ f"{pair.chain:<10} {risk_str:<15} {pair_str:<18}"
116
+ )
117
+
118
+ lines.append("=" * 90)
119
+ lines.append(f"Total: {len(pairs)} new pairs")
120
+
121
+ return "\n".join(lines)
122
+
123
+
124
+ def format_launch_detail(
125
+ pair: Any,
126
+ token_info: Any,
127
+ analysis: Any,
128
+ chain_config: Any
129
+ ) -> str:
130
+ """Format detailed launch info.
131
+
132
+ Args:
133
+ pair: PairCreated event
134
+ token_info: TokenInfo object
135
+ analysis: ContractAnalysis object
136
+ chain_config: ChainConfig object
137
+
138
+ Returns:
139
+ Formatted detail string
140
+ """
141
+ lines = [
142
+ "",
143
+ f"TOKEN LAUNCH: {token_info.symbol if token_info else 'Unknown'}",
144
+ "=" * 60,
145
+ f"Name: {token_info.name if token_info else 'Unknown'}",
146
+ f"Symbol: {token_info.symbol if token_info else '???'}",
147
+ f"Address: {pair.token0}",
148
+ f"Pair: {pair.pair_address}",
149
+ "",
150
+ "LAUNCH INFO",
151
+ "-" * 60,
152
+ f"DEX: {pair.dex}",
153
+ f"Chain: {pair.chain.upper()}",
154
+ f"Block: {pair.block_number:,}",
155
+ f"Time: {format_timestamp(pair.timestamp)} ({format_age(pair.timestamp)})",
156
+ f"Tx: {pair.tx_hash}",
157
+ "",
158
+ ]
159
+
160
+ if token_info:
161
+ lines.extend([
162
+ "TOKEN INFO",
163
+ "-" * 60,
164
+ f"Decimals: {token_info.decimals}",
165
+ f"Total Supply: {format_supply(token_info.total_supply, token_info.decimals)}",
166
+ f"Owner: {format_address(token_info.owner) if token_info.owner else 'None'}",
167
+ f"Verified: {'Yes' if token_info.is_verified else 'No'}",
168
+ "",
169
+ ])
170
+
171
+ if analysis:
172
+ lines.extend([
173
+ "RISK ANALYSIS",
174
+ "-" * 60,
175
+ f"Risk Score: {analysis.risk_score}/100 {format_risk_badge(analysis.risk_score)}",
176
+ f"Is Proxy: {'Yes' if analysis.is_proxy else 'No'}",
177
+ f"Ownership: {'Renounced' if analysis.ownership_renounced else 'Active'}",
178
+ "",
179
+ "Indicators:",
180
+ ])
181
+
182
+ for ind in analysis.indicators:
183
+ severity_marker = {
184
+ "high": "!!",
185
+ "medium": "! ",
186
+ "low": ". ",
187
+ "info": " ",
188
+ }.get(ind.severity, " ")
189
+ lines.append(f" {severity_marker} {ind.name}: {ind.description}")
190
+
191
+ lines.append("")
192
+ lines.append("LINKS")
193
+ lines.append("-" * 60)
194
+ lines.append(f"Explorer: {chain_config.explorer_url}/address/{pair.token0}")
195
+ lines.append(f"DEXScreener: https://dexscreener.com/{pair.chain}/{pair.pair_address}")
196
+
197
+ lines.append("=" * 60)
198
+
199
+ return "\n".join(lines)
200
+
201
+
202
+ def format_chain_summary(pairs_by_chain: Dict[str, int]) -> str:
203
+ """Format summary by chain.
204
+
205
+ Args:
206
+ pairs_by_chain: Dict of chain -> count
207
+
208
+ Returns:
209
+ Formatted summary
210
+ """
211
+ lines = [
212
+ "",
213
+ "LAUNCHES BY CHAIN",
214
+ "=" * 40,
215
+ ]
216
+
217
+ total = 0
218
+ for chain, count in sorted(pairs_by_chain.items(), key=lambda x: -x[1]):
219
+ lines.append(f" {chain.upper():<15} {count:>10}")
220
+ total += count
221
+
222
+ lines.append("-" * 40)
223
+ lines.append(f" {'TOTAL':<15} {total:>10}")
224
+ lines.append("=" * 40)
225
+
226
+ return "\n".join(lines)
227
+
228
+
229
+ def format_dex_summary(pairs_by_dex: Dict[str, int]) -> str:
230
+ """Format summary by DEX.
231
+
232
+ Args:
233
+ pairs_by_dex: Dict of dex -> count
234
+
235
+ Returns:
236
+ Formatted summary
237
+ """
238
+ lines = [
239
+ "",
240
+ "LAUNCHES BY DEX",
241
+ "=" * 40,
242
+ ]
243
+
244
+ for dex, count in sorted(pairs_by_dex.items(), key=lambda x: -x[1]):
245
+ lines.append(f" {dex:<25} {count:>10}")
246
+
247
+ lines.append("=" * 40)
248
+
249
+ return "\n".join(lines)
250
+
251
+
252
+ def format_json(data: Any) -> str:
253
+ """Format data as JSON."""
254
+ if hasattr(data, "__dict__"):
255
+ return json.dumps(vars(data), indent=2, default=str)
256
+ elif isinstance(data, list):
257
+ return json.dumps(
258
+ [vars(x) if hasattr(x, "__dict__") else x for x in data],
259
+ indent=2,
260
+ default=str
261
+ )
262
+ else:
263
+ return json.dumps(data, indent=2, default=str)
264
+
265
+
266
+ def main():
267
+ """CLI entry point for testing."""
268
+ print("=== Formatter Tests ===")
269
+ print(f"Timestamp: {format_timestamp(1705784400)}")
270
+ print(f"Age: {format_age(1705784400)}")
271
+ print(f"Address: {format_address('0x1234567890abcdef1234567890abcdef12345678')}")
272
+ print(f"Risk: {format_risk_badge(75)}")
273
+ print(f"Risk: {format_risk_badge(45)}")
274
+ print(f"Risk: {format_risk_badge(15)}")
275
+ print(f"Supply: {format_supply(1000000000000000000000000, 18)}")
276
+
277
+
278
+ if __name__ == "__main__":
279
+ main()