@intentsolutionsio/token-launch-tracker 1.0.0 → 1.0.7

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.
@@ -41,11 +41,7 @@ def cmd_recent(args) -> int:
41
41
  print()
42
42
 
43
43
  try:
44
- monitor = EventMonitor(
45
- chain=args.chain,
46
- rpc_url=args.rpc_url,
47
- verbose=args.verbose
48
- )
44
+ monitor = EventMonitor(chain=args.chain, rpc_url=args.rpc_url, verbose=args.verbose)
49
45
 
50
46
  pairs = monitor.get_recent_pairs(hours=args.hours, dex=args.dex)
51
47
 
@@ -54,18 +50,14 @@ def cmd_recent(args) -> int:
54
50
  return 0
55
51
 
56
52
  # Limit results
57
- pairs = pairs[:args.limit]
53
+ pairs = pairs[: args.limit]
58
54
 
59
55
  # Enrich with token info and analysis if requested
60
56
  token_infos: Dict[str, TokenInfo] = {}
61
57
  analyses: Dict[str, ContractAnalysis] = {}
62
58
 
63
59
  if args.analyze:
64
- analyzer = TokenAnalyzer(
65
- chain=args.chain,
66
- rpc_url=args.rpc_url,
67
- verbose=args.verbose
68
- )
60
+ analyzer = TokenAnalyzer(chain=args.chain, rpc_url=args.rpc_url, verbose=args.verbose)
69
61
 
70
62
  print(f"Analyzing {len(pairs)} pairs...")
71
63
  for i, pair in enumerate(pairs):
@@ -82,7 +74,7 @@ def cmd_recent(args) -> int:
82
74
  analyses[new_token] = analysis
83
75
 
84
76
  if args.verbose:
85
- print(f" [{i+1}/{len(pairs)}] {info.symbol if info else 'Unknown'}")
77
+ print(f" [{i + 1}/{len(pairs)}] {info.symbol if info else 'Unknown'}")
86
78
 
87
79
  # Output
88
80
  if args.format == "json":
@@ -105,6 +97,7 @@ def cmd_recent(args) -> int:
105
97
  print(f"Error: {e}")
106
98
  if args.verbose:
107
99
  import traceback
100
+
108
101
  traceback.print_exc()
109
102
  return 1
110
103
 
@@ -114,17 +107,10 @@ def cmd_detail(args) -> int:
114
107
  print(f"\nFetching details for {args.address[:20]}...")
115
108
 
116
109
  try:
117
- monitor = EventMonitor(
118
- chain=args.chain,
119
- rpc_url=args.rpc_url,
120
- verbose=args.verbose
121
- )
110
+ EventMonitor(chain=args.chain, rpc_url=args.rpc_url, verbose=args.verbose)
122
111
 
123
112
  analyzer = TokenAnalyzer(
124
- chain=args.chain,
125
- rpc_url=args.rpc_url,
126
- etherscan_api_key=args.etherscan_key,
127
- verbose=args.verbose
113
+ chain=args.chain, rpc_url=args.rpc_url, etherscan_api_key=args.etherscan_key, verbose=args.verbose
128
114
  )
129
115
 
130
116
  # Get token info
@@ -165,6 +151,7 @@ def cmd_detail(args) -> int:
165
151
  print(f"Error: {e}")
166
152
  if args.verbose:
167
153
  import traceback
154
+
168
155
  traceback.print_exc()
169
156
  return 1
170
157
 
@@ -175,10 +162,7 @@ def cmd_risk(args) -> int:
175
162
 
176
163
  try:
177
164
  analyzer = TokenAnalyzer(
178
- chain=args.chain,
179
- rpc_url=args.rpc_url,
180
- etherscan_api_key=args.etherscan_key,
181
- verbose=args.verbose
165
+ chain=args.chain, rpc_url=args.rpc_url, etherscan_api_key=args.etherscan_key, verbose=args.verbose
182
166
  )
183
167
 
184
168
  analysis = analyzer.analyze_contract(args.address)
@@ -214,7 +198,9 @@ def cmd_risk(args) -> int:
214
198
  print("-" * 60)
215
199
 
216
200
  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)):
201
+ for ind in sorted(
202
+ analysis.indicators, key=lambda x: {"high": 0, "medium": 1, "low": 2, "info": 3}.get(x.severity, 4)
203
+ ):
218
204
  severity_marker = {
219
205
  "high": "!!",
220
206
  "medium": "! ",
@@ -235,6 +221,7 @@ def cmd_risk(args) -> int:
235
221
  print(f"Error: {e}")
236
222
  if args.verbose:
237
223
  import traceback
224
+
238
225
  traceback.print_exc()
239
226
  return 1
240
227
 
@@ -243,7 +230,7 @@ def cmd_summary(args) -> int:
243
230
  """Show launch summary statistics."""
244
231
  chains = args.chains.split(",") if args.chains else list(CHAINS.keys())
245
232
 
246
- print(f"\nGenerating launch summary...")
233
+ print("\nGenerating launch summary...")
247
234
  print(f"Chains: {', '.join(chains)}")
248
235
  print(f"Period: Last {args.hours} hours")
249
236
  print()
@@ -295,6 +282,7 @@ def cmd_summary(args) -> int:
295
282
  print(f"Error: {e}")
296
283
  if args.verbose:
297
284
  import traceback
285
+
298
286
  traceback.print_exc()
299
287
  return 1
300
288
 
@@ -321,7 +309,7 @@ def cmd_dexes(args) -> int:
321
309
  "version": f.version,
322
310
  }
323
311
  for f in factories.values()
324
- ]
312
+ ],
325
313
  }
326
314
  print(json.dumps(output, indent=2))
327
315
  else:
@@ -339,8 +327,7 @@ def cmd_dexes(args) -> int:
339
327
  for chain_id in CHAINS:
340
328
  factories = get_dex_factories(chain_id)
341
329
  output[chain_id] = [
342
- {"name": f.name, "address": f.address, "version": f.version}
343
- for f in factories.values()
330
+ {"name": f.name, "address": f.address, "version": f.version} for f in factories.values()
344
331
  ]
345
332
  print(json.dumps(output, indent=2))
346
333
  else:
@@ -380,7 +367,9 @@ def cmd_chains(args) -> int:
380
367
  print(f"{'Chain':<15} {'Name':<20} {'ID':<10} {'Symbol':<8} {'Block':<8}")
381
368
  print("-" * 70)
382
369
  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")
370
+ print(
371
+ f"{chain_id:<15} {config.name:<20} {config.chain_id:<10} {config.native_symbol:<8} {config.block_time}s"
372
+ )
384
373
  print("=" * 70)
385
374
 
386
375
  return 0
@@ -407,156 +396,58 @@ Examples:
407
396
 
408
397
  # List supported DEXes
409
398
  %(prog)s dexes --chain bsc
410
- """
399
+ """,
411
400
  )
412
401
 
402
+ parser.add_argument("-v", "--verbose", action="store_true", help="Enable verbose output")
413
403
  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)"
404
+ "-f", "--format", choices=["text", "json"], default="text", help="Output format (default: text)"
423
405
  )
424
406
 
425
407
  subparsers = parser.add_subparsers(dest="command", help="Command")
426
408
 
427
409
  # 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
- )
410
+ recent_parser = subparsers.add_parser("recent", help="Show recently launched tokens")
411
+ recent_parser.add_argument("--chain", "-c", default="ethereum", help="Chain to scan (default: ethereum)")
412
+ recent_parser.add_argument("--hours", "-H", type=int, default=24, help="Hours to look back (default: 24)")
413
+ recent_parser.add_argument("--dex", "-d", help="Filter by DEX name")
414
+ recent_parser.add_argument("--limit", "-l", type=int, default=50, help="Maximum results (default: 50)")
415
+ recent_parser.add_argument("--analyze", "-a", action="store_true", help="Include token analysis")
416
+ recent_parser.add_argument("--skip-analysis", action="store_true", help="Skip contract analysis (faster)")
417
+ recent_parser.add_argument("--rpc-url", help="Custom RPC URL")
467
418
  recent_parser.set_defaults(func=cmd_recent)
468
419
 
469
420
  # 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
- )
421
+ detail_parser = subparsers.add_parser("detail", help="Show detailed launch information")
422
+ detail_parser.add_argument("--address", "-a", required=True, help="Token contract address")
423
+ detail_parser.add_argument("--chain", "-c", default="ethereum", help="Chain (default: ethereum)")
424
+ detail_parser.add_argument("--pair", "-p", help="Pair address (optional)")
425
+ detail_parser.add_argument("--dex", "-d", help="DEX name (optional)")
426
+ detail_parser.add_argument("--etherscan-key", help="Etherscan API key for verification check")
427
+ detail_parser.add_argument("--rpc-url", help="Custom RPC URL")
500
428
  detail_parser.set_defaults(func=cmd_detail)
501
429
 
502
430
  # 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
- )
431
+ risk_parser = subparsers.add_parser("risk", help="Analyze token contract for risks")
432
+ risk_parser.add_argument("--address", "-a", required=True, help="Token contract address")
433
+ risk_parser.add_argument("--chain", "-c", default="ethereum", help="Chain (default: ethereum)")
434
+ risk_parser.add_argument("--etherscan-key", help="Etherscan API key for verification check")
435
+ risk_parser.add_argument("--rpc-url", help="Custom RPC URL")
525
436
  risk_parser.set_defaults(func=cmd_risk)
526
437
 
527
438
  # 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
- )
439
+ summary_parser = subparsers.add_parser("summary", help="Show launch summary statistics")
440
+ summary_parser.add_argument("--chains", help="Comma-separated chains (default: all)")
441
+ summary_parser.add_argument("--hours", "-H", type=int, default=24, help="Hours to look back (default: 24)")
542
442
  summary_parser.set_defaults(func=cmd_summary)
543
443
 
544
444
  # 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
- )
445
+ dexes_parser = subparsers.add_parser("dexes", help="List supported DEXes")
446
+ dexes_parser.add_argument("--chain", "-c", help="Show DEXes for specific chain")
553
447
  dexes_parser.set_defaults(func=cmd_dexes)
554
448
 
555
449
  # chains command
556
- chains_parser = subparsers.add_parser(
557
- "chains",
558
- help="List supported chains"
559
- )
450
+ chains_parser = subparsers.add_parser("chains", help="List supported chains")
560
451
  chains_parser.set_defaults(func=cmd_chains)
561
452
 
562
453
  args = parser.parse_args()
@@ -24,6 +24,7 @@ from dex_sources import get_chain_config
24
24
  @dataclass
25
25
  class TokenInfo:
26
26
  """Complete token information."""
27
+
27
28
  address: str
28
29
  name: str
29
30
  symbol: str
@@ -37,6 +38,7 @@ class TokenInfo:
37
38
  @dataclass
38
39
  class RiskIndicator:
39
40
  """Single risk indicator."""
41
+
40
42
  name: str
41
43
  detected: bool
42
44
  severity: str # high, medium, low, info
@@ -46,6 +48,7 @@ class RiskIndicator:
46
48
  @dataclass
47
49
  class ContractAnalysis:
48
50
  """Complete contract risk analysis."""
51
+
49
52
  address: str
50
53
  risk_score: int # 0-100 (higher = riskier)
51
54
  indicators: List[RiskIndicator] = field(default_factory=list)
@@ -79,11 +82,7 @@ class TokenAnalyzer:
79
82
  """Analyze token contracts for risks."""
80
83
 
81
84
  def __init__(
82
- self,
83
- chain: str = "ethereum",
84
- rpc_url: str = None,
85
- etherscan_api_key: str = None,
86
- verbose: bool = False
85
+ self, chain: str = "ethereum", rpc_url: str = None, etherscan_api_key: str = None, verbose: bool = False
87
86
  ):
88
87
  """Initialize token analyzer.
89
88
 
@@ -95,10 +94,7 @@ class TokenAnalyzer:
95
94
  """
96
95
  self.chain = chain.lower()
97
96
  self.config = get_chain_config(chain)
98
- self.rpc_url = rpc_url or os.environ.get(
99
- f"{chain.upper()}_RPC_URL",
100
- self.config.rpc_url
101
- )
97
+ self.rpc_url = rpc_url or os.environ.get(f"{chain.upper()}_RPC_URL", self.config.rpc_url)
102
98
  self.etherscan_key = etherscan_api_key or os.environ.get("ETHERSCAN_API_KEY", "")
103
99
  self.verbose = verbose
104
100
 
@@ -129,10 +125,7 @@ class TokenAnalyzer:
129
125
  def _call_contract(self, address: str, data: str) -> Optional[str]:
130
126
  """Make eth_call."""
131
127
  try:
132
- result = self._rpc_call("eth_call", [
133
- {"to": address, "data": data},
134
- "latest"
135
- ])
128
+ result = self._rpc_call("eth_call", [{"to": address, "data": data}, "latest"])
136
129
  return result if result and result != "0x" else None
137
130
  except Exception:
138
131
  return None
@@ -176,7 +169,7 @@ class TokenAnalyzer:
176
169
 
177
170
  if len(data) >= 128:
178
171
  length = int(data[64:128], 16)
179
- string_data = data[128:128 + length * 2]
172
+ string_data = data[128 : 128 + length * 2]
180
173
  return bytes.fromhex(string_data).decode("utf-8", errors="ignore")
181
174
 
182
175
  return bytes.fromhex(data).decode("utf-8", errors="ignore").strip("\x00")
@@ -203,12 +196,16 @@ class TokenAnalyzer:
203
196
  if not api_url:
204
197
  return False
205
198
 
206
- response = requests.get(api_url, params={
207
- "module": "contract",
208
- "action": "getsourcecode",
209
- "address": address,
210
- "apikey": self.etherscan_key,
211
- }, timeout=10)
199
+ response = requests.get(
200
+ api_url,
201
+ params={
202
+ "module": "contract",
203
+ "action": "getsourcecode",
204
+ "address": address,
205
+ "apikey": self.etherscan_key,
206
+ },
207
+ timeout=10,
208
+ )
212
209
 
213
210
  data = response.json()
214
211
  if data.get("status") == "1" and data.get("result"):
@@ -240,12 +237,14 @@ class TokenAnalyzer:
240
237
  # Check if proxy
241
238
  is_proxy = self._detect_proxy(address, bytecode)
242
239
  if is_proxy:
243
- indicators.append(RiskIndicator(
244
- name="Proxy Contract",
245
- detected=True,
246
- severity="medium",
247
- description="Contract is a proxy - implementation can be changed"
248
- ))
240
+ indicators.append(
241
+ RiskIndicator(
242
+ name="Proxy Contract",
243
+ detected=True,
244
+ severity="medium",
245
+ description="Contract is a proxy - implementation can be changed",
246
+ )
247
+ )
249
248
  risk_score += 20
250
249
 
251
250
  # Check ownership
@@ -255,30 +254,29 @@ class TokenAnalyzer:
255
254
 
256
255
  if owner:
257
256
  if ownership_renounced:
258
- indicators.append(RiskIndicator(
259
- name="Ownership Renounced",
260
- detected=True,
261
- severity="info",
262
- description="Ownership has been renounced"
263
- ))
257
+ indicators.append(
258
+ RiskIndicator(
259
+ name="Ownership Renounced",
260
+ detected=True,
261
+ severity="info",
262
+ description="Ownership has been renounced",
263
+ )
264
+ )
264
265
  else:
265
- indicators.append(RiskIndicator(
266
- name="Has Owner",
267
- detected=True,
268
- severity="low",
269
- description=f"Contract has active owner: {owner[:20]}..."
270
- ))
266
+ indicators.append(
267
+ RiskIndicator(
268
+ name="Has Owner",
269
+ detected=True,
270
+ severity="low",
271
+ description=f"Contract has active owner: {owner[:20]}...",
272
+ )
273
+ )
271
274
  risk_score += 10
272
275
 
273
276
  # Check for risky function signatures in bytecode
274
277
  for sig, (name, severity, desc) in RISKY_FUNCTIONS.items():
275
278
  if sig[2:] in bytecode.lower():
276
- indicators.append(RiskIndicator(
277
- name=f"Has {name}",
278
- detected=True,
279
- severity=severity,
280
- description=desc
281
- ))
279
+ indicators.append(RiskIndicator(name=f"Has {name}", detected=True, severity=severity, description=desc))
282
280
  if severity == "high":
283
281
  risk_score += 30
284
282
  elif severity == "medium":
@@ -289,12 +287,9 @@ class TokenAnalyzer:
289
287
  # Check for suspicious patterns
290
288
  for pattern, severity, desc in SUSPICIOUS_PATTERNS:
291
289
  if pattern in bytecode.lower():
292
- indicators.append(RiskIndicator(
293
- name="Suspicious Pattern",
294
- detected=True,
295
- severity=severity,
296
- description=desc
297
- ))
290
+ indicators.append(
291
+ RiskIndicator(name="Suspicious Pattern", detected=True, severity=severity, description=desc)
292
+ )
298
293
  if severity == "high":
299
294
  risk_score += 25
300
295
  elif severity == "medium":
@@ -303,22 +298,26 @@ class TokenAnalyzer:
303
298
  # Check verification
304
299
  is_verified = self._check_verified(address)
305
300
  if not is_verified:
306
- indicators.append(RiskIndicator(
307
- name="Not Verified",
308
- detected=True,
309
- severity="medium",
310
- description="Contract source code not verified"
311
- ))
301
+ indicators.append(
302
+ RiskIndicator(
303
+ name="Not Verified",
304
+ detected=True,
305
+ severity="medium",
306
+ description="Contract source code not verified",
307
+ )
308
+ )
312
309
  risk_score += 15
313
310
 
314
311
  # Very small bytecode might be suspicious
315
312
  if bytecode_size < 500:
316
- indicators.append(RiskIndicator(
317
- name="Small Contract",
318
- detected=True,
319
- severity="info",
320
- description=f"Bytecode is only {bytecode_size} bytes"
321
- ))
313
+ indicators.append(
314
+ RiskIndicator(
315
+ name="Small Contract",
316
+ detected=True,
317
+ severity="info",
318
+ description=f"Bytecode is only {bytecode_size} bytes",
319
+ )
320
+ )
322
321
 
323
322
  # Cap at 100
324
323
  risk_score = min(risk_score, 100)
@@ -1,32 +0,0 @@
1
- #!/bin/bash
2
- # Skill validation helper
3
- # Validates skill activation and functionality
4
-
5
- set -e
6
-
7
- echo "🔍 Validating skill..."
8
-
9
- # Check if SKILL.md exists
10
- if [ ! -f "../SKILL.md" ]; then
11
- echo "❌ Error: SKILL.md not found"
12
- exit 1
13
- fi
14
-
15
- # Validate frontmatter
16
- if ! grep -q "^---$" "../SKILL.md"; then
17
- echo "❌ Error: No frontmatter found"
18
- exit 1
19
- fi
20
-
21
- # Check required fields
22
- if ! grep -q "^name:" "../SKILL.md"; then
23
- echo "❌ Error: Missing 'name' field"
24
- exit 1
25
- fi
26
-
27
- if ! grep -q "^description:" "../SKILL.md"; then
28
- echo "❌ Error: Missing 'description' field"
29
- exit 1
30
- fi
31
-
32
- echo "✅ Skill validation passed"