@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.
- package/.claude-plugin/plugin.json +22 -0
- package/LICENSE +21 -0
- package/README.md +162 -0
- package/agents/launch-tracker-agent.md +338 -0
- package/package.json +43 -0
- package/skills/skill-adapter/assets/README.md +5 -0
- package/skills/skill-adapter/assets/config-template.json +32 -0
- package/skills/skill-adapter/assets/skill-schema.json +28 -0
- package/skills/skill-adapter/assets/test-data.json +27 -0
- package/skills/skill-adapter/references/README.md +4 -0
- package/skills/skill-adapter/references/best-practices.md +69 -0
- package/skills/skill-adapter/references/examples.md +73 -0
- package/skills/skill-adapter/scripts/README.md +8 -0
- package/skills/skill-adapter/scripts/helper-template.sh +42 -0
- package/skills/skill-adapter/scripts/validation.sh +32 -0
- package/skills/tracking-token-launches/ARD.md +183 -0
- package/skills/tracking-token-launches/PRD.md +66 -0
- package/skills/tracking-token-launches/SKILL.md +161 -0
- package/skills/tracking-token-launches/config/settings.yaml +166 -0
- package/skills/tracking-token-launches/references/errors.md +167 -0
- package/skills/tracking-token-launches/references/examples.md +292 -0
- package/skills/tracking-token-launches/references/implementation.md +36 -0
- package/skills/tracking-token-launches/scripts/dex_sources.py +270 -0
- package/skills/tracking-token-launches/scripts/event_monitor.py +345 -0
- package/skills/tracking-token-launches/scripts/formatters.py +279 -0
- package/skills/tracking-token-launches/scripts/launch_tracker.py +572 -0
- package/skills/tracking-token-launches/scripts/token_analyzer.py +417 -0
|
@@ -0,0 +1,572 @@
|
|
|
1
|
+
#!/usr/bin/env python3
|
|
2
|
+
"""
|
|
3
|
+
Token Launch Tracker
|
|
4
|
+
|
|
5
|
+
Track new token launches across DEXes with risk analysis.
|
|
6
|
+
|
|
7
|
+
Author: Jeremy Longshore <jeremy@intentsolutions.io>
|
|
8
|
+
Version: 1.0.0
|
|
9
|
+
License: MIT
|
|
10
|
+
"""
|
|
11
|
+
|
|
12
|
+
import argparse
|
|
13
|
+
import json
|
|
14
|
+
import sys
|
|
15
|
+
from datetime import datetime
|
|
16
|
+
from typing import Dict, List
|
|
17
|
+
|
|
18
|
+
# Local imports
|
|
19
|
+
from dex_sources import (
|
|
20
|
+
get_chain_config,
|
|
21
|
+
get_dex_factories,
|
|
22
|
+
CHAINS,
|
|
23
|
+
)
|
|
24
|
+
from event_monitor import EventMonitor, PairCreated
|
|
25
|
+
from token_analyzer import TokenAnalyzer, TokenInfo, ContractAnalysis
|
|
26
|
+
from formatters import (
|
|
27
|
+
format_new_pairs_table,
|
|
28
|
+
format_launch_detail,
|
|
29
|
+
format_chain_summary,
|
|
30
|
+
format_dex_summary,
|
|
31
|
+
format_risk_badge,
|
|
32
|
+
)
|
|
33
|
+
|
|
34
|
+
|
|
35
|
+
def cmd_recent(args) -> int:
|
|
36
|
+
"""Show recently launched tokens."""
|
|
37
|
+
print(f"\nScanning {args.chain.upper()} for new launches...")
|
|
38
|
+
print(f"Looking back {args.hours} hours")
|
|
39
|
+
if args.dex:
|
|
40
|
+
print(f"Filtering by DEX: {args.dex}")
|
|
41
|
+
print()
|
|
42
|
+
|
|
43
|
+
try:
|
|
44
|
+
monitor = EventMonitor(
|
|
45
|
+
chain=args.chain,
|
|
46
|
+
rpc_url=args.rpc_url,
|
|
47
|
+
verbose=args.verbose
|
|
48
|
+
)
|
|
49
|
+
|
|
50
|
+
pairs = monitor.get_recent_pairs(hours=args.hours, dex=args.dex)
|
|
51
|
+
|
|
52
|
+
if not pairs:
|
|
53
|
+
print("No new pairs found in the specified timeframe.")
|
|
54
|
+
return 0
|
|
55
|
+
|
|
56
|
+
# Limit results
|
|
57
|
+
pairs = pairs[:args.limit]
|
|
58
|
+
|
|
59
|
+
# Enrich with token info and analysis if requested
|
|
60
|
+
token_infos: Dict[str, TokenInfo] = {}
|
|
61
|
+
analyses: Dict[str, ContractAnalysis] = {}
|
|
62
|
+
|
|
63
|
+
if args.analyze:
|
|
64
|
+
analyzer = TokenAnalyzer(
|
|
65
|
+
chain=args.chain,
|
|
66
|
+
rpc_url=args.rpc_url,
|
|
67
|
+
verbose=args.verbose
|
|
68
|
+
)
|
|
69
|
+
|
|
70
|
+
print(f"Analyzing {len(pairs)} pairs...")
|
|
71
|
+
for i, pair in enumerate(pairs):
|
|
72
|
+
new_token = monitor.identify_new_token(pair)
|
|
73
|
+
|
|
74
|
+
# Get token info
|
|
75
|
+
info = analyzer.get_token_info(new_token)
|
|
76
|
+
if info:
|
|
77
|
+
token_infos[new_token] = info
|
|
78
|
+
|
|
79
|
+
# Analyze contract
|
|
80
|
+
if not args.skip_analysis:
|
|
81
|
+
analysis = analyzer.analyze_contract(new_token)
|
|
82
|
+
analyses[new_token] = analysis
|
|
83
|
+
|
|
84
|
+
if args.verbose:
|
|
85
|
+
print(f" [{i+1}/{len(pairs)}] {info.symbol if info else 'Unknown'}")
|
|
86
|
+
|
|
87
|
+
# Output
|
|
88
|
+
if args.format == "json":
|
|
89
|
+
output = []
|
|
90
|
+
for pair in pairs:
|
|
91
|
+
new_token = monitor.identify_new_token(pair)
|
|
92
|
+
entry = {
|
|
93
|
+
"pair": vars(pair),
|
|
94
|
+
"token_info": vars(token_infos.get(new_token)) if new_token in token_infos else None,
|
|
95
|
+
"analysis": vars(analyses.get(new_token)) if new_token in analyses else None,
|
|
96
|
+
}
|
|
97
|
+
output.append(entry)
|
|
98
|
+
print(json.dumps(output, indent=2, default=str))
|
|
99
|
+
else:
|
|
100
|
+
print(format_new_pairs_table(pairs, token_infos, analyses))
|
|
101
|
+
|
|
102
|
+
return 0
|
|
103
|
+
|
|
104
|
+
except Exception as e:
|
|
105
|
+
print(f"Error: {e}")
|
|
106
|
+
if args.verbose:
|
|
107
|
+
import traceback
|
|
108
|
+
traceback.print_exc()
|
|
109
|
+
return 1
|
|
110
|
+
|
|
111
|
+
|
|
112
|
+
def cmd_detail(args) -> int:
|
|
113
|
+
"""Show detailed launch information."""
|
|
114
|
+
print(f"\nFetching details for {args.address[:20]}...")
|
|
115
|
+
|
|
116
|
+
try:
|
|
117
|
+
monitor = EventMonitor(
|
|
118
|
+
chain=args.chain,
|
|
119
|
+
rpc_url=args.rpc_url,
|
|
120
|
+
verbose=args.verbose
|
|
121
|
+
)
|
|
122
|
+
|
|
123
|
+
analyzer = TokenAnalyzer(
|
|
124
|
+
chain=args.chain,
|
|
125
|
+
rpc_url=args.rpc_url,
|
|
126
|
+
etherscan_api_key=args.etherscan_key,
|
|
127
|
+
verbose=args.verbose
|
|
128
|
+
)
|
|
129
|
+
|
|
130
|
+
# Get token info
|
|
131
|
+
token_info = analyzer.get_token_info(args.address)
|
|
132
|
+
|
|
133
|
+
# Analyze contract
|
|
134
|
+
analysis = analyzer.analyze_contract(args.address)
|
|
135
|
+
|
|
136
|
+
# Get chain config
|
|
137
|
+
chain_config = get_chain_config(args.chain)
|
|
138
|
+
|
|
139
|
+
# Create a mock pair for display
|
|
140
|
+
mock_pair = PairCreated(
|
|
141
|
+
block_number=0,
|
|
142
|
+
tx_hash="",
|
|
143
|
+
timestamp=int(datetime.now().timestamp()),
|
|
144
|
+
pair_address=args.pair or "Unknown",
|
|
145
|
+
token0=args.address,
|
|
146
|
+
token1="",
|
|
147
|
+
dex=args.dex or "Unknown",
|
|
148
|
+
chain=args.chain,
|
|
149
|
+
factory_address="",
|
|
150
|
+
)
|
|
151
|
+
|
|
152
|
+
if args.format == "json":
|
|
153
|
+
output = {
|
|
154
|
+
"token_info": vars(token_info) if token_info else None,
|
|
155
|
+
"analysis": vars(analysis),
|
|
156
|
+
"risk_summary": analyzer.get_risk_summary(analysis),
|
|
157
|
+
}
|
|
158
|
+
print(json.dumps(output, indent=2, default=str))
|
|
159
|
+
else:
|
|
160
|
+
print(format_launch_detail(mock_pair, token_info, analysis, chain_config))
|
|
161
|
+
|
|
162
|
+
return 0
|
|
163
|
+
|
|
164
|
+
except Exception as e:
|
|
165
|
+
print(f"Error: {e}")
|
|
166
|
+
if args.verbose:
|
|
167
|
+
import traceback
|
|
168
|
+
traceback.print_exc()
|
|
169
|
+
return 1
|
|
170
|
+
|
|
171
|
+
|
|
172
|
+
def cmd_risk(args) -> int:
|
|
173
|
+
"""Analyze token contract for risks."""
|
|
174
|
+
print(f"\nAnalyzing contract: {args.address[:20]}...")
|
|
175
|
+
|
|
176
|
+
try:
|
|
177
|
+
analyzer = TokenAnalyzer(
|
|
178
|
+
chain=args.chain,
|
|
179
|
+
rpc_url=args.rpc_url,
|
|
180
|
+
etherscan_api_key=args.etherscan_key,
|
|
181
|
+
verbose=args.verbose
|
|
182
|
+
)
|
|
183
|
+
|
|
184
|
+
analysis = analyzer.analyze_contract(args.address)
|
|
185
|
+
|
|
186
|
+
if args.format == "json":
|
|
187
|
+
output = {
|
|
188
|
+
"address": args.address,
|
|
189
|
+
"risk_score": analysis.risk_score,
|
|
190
|
+
"risk_level": analyzer.get_risk_summary(analysis),
|
|
191
|
+
"is_proxy": analysis.is_proxy,
|
|
192
|
+
"ownership_renounced": analysis.ownership_renounced,
|
|
193
|
+
"bytecode_size": analysis.bytecode_size,
|
|
194
|
+
"indicators": [vars(ind) for ind in analysis.indicators],
|
|
195
|
+
}
|
|
196
|
+
print(json.dumps(output, indent=2, default=str))
|
|
197
|
+
else:
|
|
198
|
+
print()
|
|
199
|
+
print("RISK ANALYSIS")
|
|
200
|
+
print("=" * 60)
|
|
201
|
+
print(f"Address: {args.address}")
|
|
202
|
+
print(f"Chain: {args.chain.upper()}")
|
|
203
|
+
print()
|
|
204
|
+
print(f"Risk Score: {analysis.risk_score}/100 {format_risk_badge(analysis.risk_score)}")
|
|
205
|
+
print(f"Risk Level: {analyzer.get_risk_summary(analysis)}")
|
|
206
|
+
print()
|
|
207
|
+
print("CONTRACT INFO")
|
|
208
|
+
print("-" * 60)
|
|
209
|
+
print(f"Bytecode: {analysis.bytecode_size:,} bytes")
|
|
210
|
+
print(f"Is Proxy: {'Yes' if analysis.is_proxy else 'No'}")
|
|
211
|
+
print(f"Ownership: {'Renounced' if analysis.ownership_renounced else 'Active'}")
|
|
212
|
+
print()
|
|
213
|
+
print("RISK INDICATORS")
|
|
214
|
+
print("-" * 60)
|
|
215
|
+
|
|
216
|
+
if analysis.indicators:
|
|
217
|
+
for ind in sorted(analysis.indicators, key=lambda x: {"high": 0, "medium": 1, "low": 2, "info": 3}.get(x.severity, 4)):
|
|
218
|
+
severity_marker = {
|
|
219
|
+
"high": "!!",
|
|
220
|
+
"medium": "! ",
|
|
221
|
+
"low": ". ",
|
|
222
|
+
"info": " ",
|
|
223
|
+
}.get(ind.severity, " ")
|
|
224
|
+
print(f" {severity_marker} [{ind.severity.upper():6}] {ind.name}")
|
|
225
|
+
print(f" {ind.description}")
|
|
226
|
+
else:
|
|
227
|
+
print(" No risk indicators detected")
|
|
228
|
+
|
|
229
|
+
print()
|
|
230
|
+
print("=" * 60)
|
|
231
|
+
|
|
232
|
+
return 0
|
|
233
|
+
|
|
234
|
+
except Exception as e:
|
|
235
|
+
print(f"Error: {e}")
|
|
236
|
+
if args.verbose:
|
|
237
|
+
import traceback
|
|
238
|
+
traceback.print_exc()
|
|
239
|
+
return 1
|
|
240
|
+
|
|
241
|
+
|
|
242
|
+
def cmd_summary(args) -> int:
|
|
243
|
+
"""Show launch summary statistics."""
|
|
244
|
+
chains = args.chains.split(",") if args.chains else list(CHAINS.keys())
|
|
245
|
+
|
|
246
|
+
print(f"\nGenerating launch summary...")
|
|
247
|
+
print(f"Chains: {', '.join(chains)}")
|
|
248
|
+
print(f"Period: Last {args.hours} hours")
|
|
249
|
+
print()
|
|
250
|
+
|
|
251
|
+
try:
|
|
252
|
+
pairs_by_chain: Dict[str, int] = {}
|
|
253
|
+
pairs_by_dex: Dict[str, int] = {}
|
|
254
|
+
all_pairs: List[PairCreated] = []
|
|
255
|
+
|
|
256
|
+
for chain in chains:
|
|
257
|
+
if chain not in CHAINS:
|
|
258
|
+
print(f"Warning: Skipping unsupported chain: {chain}")
|
|
259
|
+
continue
|
|
260
|
+
|
|
261
|
+
if args.verbose:
|
|
262
|
+
print(f"Scanning {chain}...")
|
|
263
|
+
|
|
264
|
+
try:
|
|
265
|
+
monitor = EventMonitor(chain=chain, verbose=args.verbose)
|
|
266
|
+
pairs = monitor.get_recent_pairs(hours=args.hours)
|
|
267
|
+
|
|
268
|
+
pairs_by_chain[chain] = len(pairs)
|
|
269
|
+
all_pairs.extend(pairs)
|
|
270
|
+
|
|
271
|
+
for pair in pairs:
|
|
272
|
+
dex_key = f"{chain}:{pair.dex}"
|
|
273
|
+
pairs_by_dex[dex_key] = pairs_by_dex.get(dex_key, 0) + 1
|
|
274
|
+
|
|
275
|
+
except Exception as e:
|
|
276
|
+
print(f"Error scanning {chain}: {e}")
|
|
277
|
+
pairs_by_chain[chain] = 0
|
|
278
|
+
|
|
279
|
+
if args.format == "json":
|
|
280
|
+
output = {
|
|
281
|
+
"period_hours": args.hours,
|
|
282
|
+
"chains": chains,
|
|
283
|
+
"by_chain": pairs_by_chain,
|
|
284
|
+
"by_dex": pairs_by_dex,
|
|
285
|
+
"total": sum(pairs_by_chain.values()),
|
|
286
|
+
}
|
|
287
|
+
print(json.dumps(output, indent=2))
|
|
288
|
+
else:
|
|
289
|
+
print(format_chain_summary(pairs_by_chain))
|
|
290
|
+
print(format_dex_summary(pairs_by_dex))
|
|
291
|
+
|
|
292
|
+
return 0
|
|
293
|
+
|
|
294
|
+
except Exception as e:
|
|
295
|
+
print(f"Error: {e}")
|
|
296
|
+
if args.verbose:
|
|
297
|
+
import traceback
|
|
298
|
+
traceback.print_exc()
|
|
299
|
+
return 1
|
|
300
|
+
|
|
301
|
+
|
|
302
|
+
def cmd_dexes(args) -> int:
|
|
303
|
+
"""List supported DEXes."""
|
|
304
|
+
chain = args.chain
|
|
305
|
+
|
|
306
|
+
if chain:
|
|
307
|
+
if chain not in CHAINS:
|
|
308
|
+
print(f"Error: Unsupported chain: {chain}")
|
|
309
|
+
print(f"Supported chains: {', '.join(CHAINS.keys())}")
|
|
310
|
+
return 1
|
|
311
|
+
|
|
312
|
+
factories = get_dex_factories(chain)
|
|
313
|
+
|
|
314
|
+
if args.format == "json":
|
|
315
|
+
output = {
|
|
316
|
+
"chain": chain,
|
|
317
|
+
"dexes": [
|
|
318
|
+
{
|
|
319
|
+
"name": f.name,
|
|
320
|
+
"address": f.address,
|
|
321
|
+
"version": f.version,
|
|
322
|
+
}
|
|
323
|
+
for f in factories.values()
|
|
324
|
+
]
|
|
325
|
+
}
|
|
326
|
+
print(json.dumps(output, indent=2))
|
|
327
|
+
else:
|
|
328
|
+
print()
|
|
329
|
+
print(f"SUPPORTED DEXES ON {chain.upper()}")
|
|
330
|
+
print("=" * 60)
|
|
331
|
+
for key, factory in factories.items():
|
|
332
|
+
print(f" {factory.name:<25} ({factory.version})")
|
|
333
|
+
print(f" Factory: {factory.address}")
|
|
334
|
+
print("=" * 60)
|
|
335
|
+
else:
|
|
336
|
+
# List all
|
|
337
|
+
if args.format == "json":
|
|
338
|
+
output = {}
|
|
339
|
+
for chain_id in CHAINS:
|
|
340
|
+
factories = get_dex_factories(chain_id)
|
|
341
|
+
output[chain_id] = [
|
|
342
|
+
{"name": f.name, "address": f.address, "version": f.version}
|
|
343
|
+
for f in factories.values()
|
|
344
|
+
]
|
|
345
|
+
print(json.dumps(output, indent=2))
|
|
346
|
+
else:
|
|
347
|
+
print()
|
|
348
|
+
print("SUPPORTED DEXES BY CHAIN")
|
|
349
|
+
print("=" * 60)
|
|
350
|
+
for chain_id, config in CHAINS.items():
|
|
351
|
+
factories = get_dex_factories(chain_id)
|
|
352
|
+
print(f"\n{config.name} ({chain_id})")
|
|
353
|
+
print("-" * 40)
|
|
354
|
+
for factory in factories.values():
|
|
355
|
+
print(f" {factory.name:<25} {factory.version}")
|
|
356
|
+
print()
|
|
357
|
+
print("=" * 60)
|
|
358
|
+
|
|
359
|
+
return 0
|
|
360
|
+
|
|
361
|
+
|
|
362
|
+
def cmd_chains(args) -> int:
|
|
363
|
+
"""List supported chains."""
|
|
364
|
+
if args.format == "json":
|
|
365
|
+
output = {
|
|
366
|
+
chain_id: {
|
|
367
|
+
"name": config.name,
|
|
368
|
+
"chain_id": config.chain_id,
|
|
369
|
+
"native_symbol": config.native_symbol,
|
|
370
|
+
"block_time": config.block_time,
|
|
371
|
+
"explorer_url": config.explorer_url,
|
|
372
|
+
}
|
|
373
|
+
for chain_id, config in CHAINS.items()
|
|
374
|
+
}
|
|
375
|
+
print(json.dumps(output, indent=2))
|
|
376
|
+
else:
|
|
377
|
+
print()
|
|
378
|
+
print("SUPPORTED CHAINS")
|
|
379
|
+
print("=" * 70)
|
|
380
|
+
print(f"{'Chain':<15} {'Name':<20} {'ID':<10} {'Symbol':<8} {'Block':<8}")
|
|
381
|
+
print("-" * 70)
|
|
382
|
+
for chain_id, config in CHAINS.items():
|
|
383
|
+
print(f"{chain_id:<15} {config.name:<20} {config.chain_id:<10} {config.native_symbol:<8} {config.block_time}s")
|
|
384
|
+
print("=" * 70)
|
|
385
|
+
|
|
386
|
+
return 0
|
|
387
|
+
|
|
388
|
+
|
|
389
|
+
def main():
|
|
390
|
+
"""Main entry point."""
|
|
391
|
+
parser = argparse.ArgumentParser(
|
|
392
|
+
description="Track new token launches across DEXes",
|
|
393
|
+
formatter_class=argparse.RawDescriptionHelpFormatter,
|
|
394
|
+
epilog="""
|
|
395
|
+
Examples:
|
|
396
|
+
# Show recent launches on Ethereum
|
|
397
|
+
%(prog)s recent --chain ethereum --hours 24
|
|
398
|
+
|
|
399
|
+
# Get detailed info for a specific token
|
|
400
|
+
%(prog)s detail --address 0x... --chain ethereum
|
|
401
|
+
|
|
402
|
+
# Analyze token risk
|
|
403
|
+
%(prog)s risk --address 0x... --chain base
|
|
404
|
+
|
|
405
|
+
# Show launch summary across all chains
|
|
406
|
+
%(prog)s summary --hours 24
|
|
407
|
+
|
|
408
|
+
# List supported DEXes
|
|
409
|
+
%(prog)s dexes --chain bsc
|
|
410
|
+
"""
|
|
411
|
+
)
|
|
412
|
+
|
|
413
|
+
parser.add_argument(
|
|
414
|
+
"-v", "--verbose",
|
|
415
|
+
action="store_true",
|
|
416
|
+
help="Enable verbose output"
|
|
417
|
+
)
|
|
418
|
+
parser.add_argument(
|
|
419
|
+
"-f", "--format",
|
|
420
|
+
choices=["text", "json"],
|
|
421
|
+
default="text",
|
|
422
|
+
help="Output format (default: text)"
|
|
423
|
+
)
|
|
424
|
+
|
|
425
|
+
subparsers = parser.add_subparsers(dest="command", help="Command")
|
|
426
|
+
|
|
427
|
+
# recent command
|
|
428
|
+
recent_parser = subparsers.add_parser(
|
|
429
|
+
"recent",
|
|
430
|
+
help="Show recently launched tokens"
|
|
431
|
+
)
|
|
432
|
+
recent_parser.add_argument(
|
|
433
|
+
"--chain", "-c",
|
|
434
|
+
default="ethereum",
|
|
435
|
+
help="Chain to scan (default: ethereum)"
|
|
436
|
+
)
|
|
437
|
+
recent_parser.add_argument(
|
|
438
|
+
"--hours", "-H",
|
|
439
|
+
type=int,
|
|
440
|
+
default=24,
|
|
441
|
+
help="Hours to look back (default: 24)"
|
|
442
|
+
)
|
|
443
|
+
recent_parser.add_argument(
|
|
444
|
+
"--dex", "-d",
|
|
445
|
+
help="Filter by DEX name"
|
|
446
|
+
)
|
|
447
|
+
recent_parser.add_argument(
|
|
448
|
+
"--limit", "-l",
|
|
449
|
+
type=int,
|
|
450
|
+
default=50,
|
|
451
|
+
help="Maximum results (default: 50)"
|
|
452
|
+
)
|
|
453
|
+
recent_parser.add_argument(
|
|
454
|
+
"--analyze", "-a",
|
|
455
|
+
action="store_true",
|
|
456
|
+
help="Include token analysis"
|
|
457
|
+
)
|
|
458
|
+
recent_parser.add_argument(
|
|
459
|
+
"--skip-analysis",
|
|
460
|
+
action="store_true",
|
|
461
|
+
help="Skip contract analysis (faster)"
|
|
462
|
+
)
|
|
463
|
+
recent_parser.add_argument(
|
|
464
|
+
"--rpc-url",
|
|
465
|
+
help="Custom RPC URL"
|
|
466
|
+
)
|
|
467
|
+
recent_parser.set_defaults(func=cmd_recent)
|
|
468
|
+
|
|
469
|
+
# detail command
|
|
470
|
+
detail_parser = subparsers.add_parser(
|
|
471
|
+
"detail",
|
|
472
|
+
help="Show detailed launch information"
|
|
473
|
+
)
|
|
474
|
+
detail_parser.add_argument(
|
|
475
|
+
"--address", "-a",
|
|
476
|
+
required=True,
|
|
477
|
+
help="Token contract address"
|
|
478
|
+
)
|
|
479
|
+
detail_parser.add_argument(
|
|
480
|
+
"--chain", "-c",
|
|
481
|
+
default="ethereum",
|
|
482
|
+
help="Chain (default: ethereum)"
|
|
483
|
+
)
|
|
484
|
+
detail_parser.add_argument(
|
|
485
|
+
"--pair", "-p",
|
|
486
|
+
help="Pair address (optional)"
|
|
487
|
+
)
|
|
488
|
+
detail_parser.add_argument(
|
|
489
|
+
"--dex", "-d",
|
|
490
|
+
help="DEX name (optional)"
|
|
491
|
+
)
|
|
492
|
+
detail_parser.add_argument(
|
|
493
|
+
"--etherscan-key",
|
|
494
|
+
help="Etherscan API key for verification check"
|
|
495
|
+
)
|
|
496
|
+
detail_parser.add_argument(
|
|
497
|
+
"--rpc-url",
|
|
498
|
+
help="Custom RPC URL"
|
|
499
|
+
)
|
|
500
|
+
detail_parser.set_defaults(func=cmd_detail)
|
|
501
|
+
|
|
502
|
+
# risk command
|
|
503
|
+
risk_parser = subparsers.add_parser(
|
|
504
|
+
"risk",
|
|
505
|
+
help="Analyze token contract for risks"
|
|
506
|
+
)
|
|
507
|
+
risk_parser.add_argument(
|
|
508
|
+
"--address", "-a",
|
|
509
|
+
required=True,
|
|
510
|
+
help="Token contract address"
|
|
511
|
+
)
|
|
512
|
+
risk_parser.add_argument(
|
|
513
|
+
"--chain", "-c",
|
|
514
|
+
default="ethereum",
|
|
515
|
+
help="Chain (default: ethereum)"
|
|
516
|
+
)
|
|
517
|
+
risk_parser.add_argument(
|
|
518
|
+
"--etherscan-key",
|
|
519
|
+
help="Etherscan API key for verification check"
|
|
520
|
+
)
|
|
521
|
+
risk_parser.add_argument(
|
|
522
|
+
"--rpc-url",
|
|
523
|
+
help="Custom RPC URL"
|
|
524
|
+
)
|
|
525
|
+
risk_parser.set_defaults(func=cmd_risk)
|
|
526
|
+
|
|
527
|
+
# summary command
|
|
528
|
+
summary_parser = subparsers.add_parser(
|
|
529
|
+
"summary",
|
|
530
|
+
help="Show launch summary statistics"
|
|
531
|
+
)
|
|
532
|
+
summary_parser.add_argument(
|
|
533
|
+
"--chains",
|
|
534
|
+
help="Comma-separated chains (default: all)"
|
|
535
|
+
)
|
|
536
|
+
summary_parser.add_argument(
|
|
537
|
+
"--hours", "-H",
|
|
538
|
+
type=int,
|
|
539
|
+
default=24,
|
|
540
|
+
help="Hours to look back (default: 24)"
|
|
541
|
+
)
|
|
542
|
+
summary_parser.set_defaults(func=cmd_summary)
|
|
543
|
+
|
|
544
|
+
# dexes command
|
|
545
|
+
dexes_parser = subparsers.add_parser(
|
|
546
|
+
"dexes",
|
|
547
|
+
help="List supported DEXes"
|
|
548
|
+
)
|
|
549
|
+
dexes_parser.add_argument(
|
|
550
|
+
"--chain", "-c",
|
|
551
|
+
help="Show DEXes for specific chain"
|
|
552
|
+
)
|
|
553
|
+
dexes_parser.set_defaults(func=cmd_dexes)
|
|
554
|
+
|
|
555
|
+
# chains command
|
|
556
|
+
chains_parser = subparsers.add_parser(
|
|
557
|
+
"chains",
|
|
558
|
+
help="List supported chains"
|
|
559
|
+
)
|
|
560
|
+
chains_parser.set_defaults(func=cmd_chains)
|
|
561
|
+
|
|
562
|
+
args = parser.parse_args()
|
|
563
|
+
|
|
564
|
+
if not args.command:
|
|
565
|
+
parser.print_help()
|
|
566
|
+
return 1
|
|
567
|
+
|
|
568
|
+
return args.func(args)
|
|
569
|
+
|
|
570
|
+
|
|
571
|
+
if __name__ == "__main__":
|
|
572
|
+
sys.exit(main())
|