@intentsolutionsio/token-launch-tracker 1.0.0 → 1.0.4
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/README.md +19 -9
- package/agents/launch-tracker-agent.md +26 -3
- package/package.json +1 -1
- package/skills/skill-adapter/references/README.md +0 -1
- package/skills/skill-adapter/references/examples.md +6 -0
- package/skills/tracking-token-launches/ARD.md +9 -0
- package/skills/tracking-token-launches/PRD.md +3 -0
- package/skills/tracking-token-launches/SKILL.md +30 -6
- package/skills/tracking-token-launches/references/errors.md +38 -0
- package/skills/tracking-token-launches/references/examples.md +19 -0
- package/skills/tracking-token-launches/references/implementation.md +9 -1
- package/skills/tracking-token-launches/scripts/dex_sources.py +2 -0
- package/skills/tracking-token-launches/scripts/event_monitor.py +20 -35
- package/skills/tracking-token-launches/scripts/formatters.py +27 -39
- package/skills/tracking-token-launches/scripts/launch_tracker.py +49 -158
- package/skills/tracking-token-launches/scripts/token_analyzer.py +61 -62
|
@@ -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
|
-
|
|
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(
|
|
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(
|
|
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(
|
|
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
|
-
"-
|
|
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
|
-
|
|
430
|
-
|
|
431
|
-
)
|
|
432
|
-
recent_parser.add_argument(
|
|
433
|
-
|
|
434
|
-
|
|
435
|
-
|
|
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
|
-
|
|
472
|
-
|
|
473
|
-
)
|
|
474
|
-
detail_parser.add_argument(
|
|
475
|
-
|
|
476
|
-
|
|
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
|
-
|
|
505
|
-
|
|
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
|
-
|
|
530
|
-
|
|
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
|
-
|
|
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(
|
|
207
|
-
|
|
208
|
-
|
|
209
|
-
|
|
210
|
-
|
|
211
|
-
|
|
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(
|
|
244
|
-
|
|
245
|
-
|
|
246
|
-
|
|
247
|
-
|
|
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(
|
|
259
|
-
|
|
260
|
-
|
|
261
|
-
|
|
262
|
-
|
|
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(
|
|
266
|
-
|
|
267
|
-
|
|
268
|
-
|
|
269
|
-
|
|
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(
|
|
293
|
-
name="Suspicious Pattern",
|
|
294
|
-
|
|
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(
|
|
307
|
-
|
|
308
|
-
|
|
309
|
-
|
|
310
|
-
|
|
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(
|
|
317
|
-
|
|
318
|
-
|
|
319
|
-
|
|
320
|
-
|
|
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)
|