@vizzor/cli 0.13.1 → 0.14.6
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 +251 -192
- package/chronovisor-engine/pyproject.toml +31 -0
- package/chronovisor-engine/src/__init__.py +0 -0
- package/chronovisor-engine/src/inference/__init__.py +0 -0
- package/chronovisor-engine/src/inference/predict.py +44 -0
- package/chronovisor-engine/src/model_catalog.py +219 -0
- package/chronovisor-engine/src/models/__init__.py +0 -0
- package/chronovisor-engine/src/models/anomaly_detector.py +104 -0
- package/chronovisor-engine/src/models/blockchain_cycle_analyzer.py +217 -0
- package/chronovisor-engine/src/models/catalyst_event_model.py +70 -0
- package/chronovisor-engine/src/models/conformal_interval.py +50 -0
- package/chronovisor-engine/src/models/divergence_detector.py +247 -0
- package/chronovisor-engine/src/models/drift_monitor.py +51 -0
- package/chronovisor-engine/src/models/intent_classifier.py +189 -0
- package/chronovisor-engine/src/models/lstm_predictor.py +143 -0
- package/chronovisor-engine/src/models/microstructure_specialist.py +65 -0
- package/chronovisor-engine/src/models/narrative_detector.py +418 -0
- package/chronovisor-engine/src/models/portfolio_optimizer.py +162 -0
- package/chronovisor-engine/src/models/project_risk_scorer.py +184 -0
- package/chronovisor-engine/src/models/pump_detector.py +344 -0
- package/chronovisor-engine/src/models/regime_detector.py +127 -0
- package/chronovisor-engine/src/models/rug_detector.py +197 -0
- package/chronovisor-engine/src/models/sentiment_analyzer.py +257 -0
- package/chronovisor-engine/src/models/signal_classifier.py +191 -0
- package/chronovisor-engine/src/models/stacking_meta.py +56 -0
- package/chronovisor-engine/src/models/strategy_bandit.py +191 -0
- package/chronovisor-engine/src/models/ta_interpreter.py +341 -0
- package/chronovisor-engine/src/models/target_quantile.py +96 -0
- package/chronovisor-engine/src/models/trend_scorer.py +107 -0
- package/chronovisor-engine/src/models/wallet_classifier.py +261 -0
- package/chronovisor-engine/src/server.py +1686 -0
- package/chronovisor-engine/src/training/__init__.py +0 -0
- package/chronovisor-engine/src/training/data_loader.py +635 -0
- package/chronovisor-engine/src/training/pipeline.py +130 -0
- package/chronovisor-engine/src/training/train_catalyst.py +169 -0
- package/chronovisor-engine/src/training/train_classifier.py +159 -0
- package/chronovisor-engine/src/training/train_conformal.py +106 -0
- package/chronovisor-engine/src/training/train_direction.py +215 -0
- package/chronovisor-engine/src/training/train_drift.py +57 -0
- package/chronovisor-engine/src/training/train_isotonic.py +58 -0
- package/chronovisor-engine/src/training/train_lstm.py +217 -0
- package/chronovisor-engine/src/training/train_microstructure.py +102 -0
- package/chronovisor-engine/src/training/train_narrative.py +168 -0
- package/chronovisor-engine/src/training/train_pump.py +109 -0
- package/chronovisor-engine/src/training/train_regime.py +116 -0
- package/chronovisor-engine/src/training/train_rug.py +58 -0
- package/chronovisor-engine/src/training/train_sentiment.py +63 -0
- package/chronovisor-engine/src/training/train_stacking_meta.py +74 -0
- package/chronovisor-engine/src/training/train_target_quantile.py +115 -0
- package/chronovisor-engine/src/training/train_trend.py +101 -0
- package/dist/index.js +22494 -15023
- package/dist/index.js.map +1 -1
- package/package.json +5 -1
- package/vizzor_logodarkicon.png +0 -0
- package/vizzor_logoicon.png +0 -0
|
@@ -0,0 +1,261 @@
|
|
|
1
|
+
"""LSTM-based wallet behavior classifier.
|
|
2
|
+
|
|
3
|
+
Input: transaction sequence features (tx_count, avg_value, avg_gas,
|
|
4
|
+
unique_recipients, unique_methods, time_spread, etc.)
|
|
5
|
+
Output: behavior_type classification + confidence + risk_score.
|
|
6
|
+
|
|
7
|
+
Classifies wallets as: normal_trader, bot, whale, sniper, mev_bot,
|
|
8
|
+
mixer_user, rug_deployer.
|
|
9
|
+
|
|
10
|
+
Trained on labeled wallet transaction sequences. Heuristic fallback.
|
|
11
|
+
"""
|
|
12
|
+
|
|
13
|
+
import os
|
|
14
|
+
from pathlib import Path
|
|
15
|
+
|
|
16
|
+
import numpy as np
|
|
17
|
+
|
|
18
|
+
MODEL_DIR = Path(os.getenv("MODEL_DIR", "models"))
|
|
19
|
+
|
|
20
|
+
BEHAVIOR_TYPES = [
|
|
21
|
+
"normal_trader",
|
|
22
|
+
"bot",
|
|
23
|
+
"whale",
|
|
24
|
+
"sniper",
|
|
25
|
+
"mev_bot",
|
|
26
|
+
"mixer_user",
|
|
27
|
+
"rug_deployer",
|
|
28
|
+
]
|
|
29
|
+
|
|
30
|
+
# Aggregated transaction features (from raw tx sequence)
|
|
31
|
+
AGG_FEATURE_KEYS = [
|
|
32
|
+
"tx_count",
|
|
33
|
+
"avg_value_eth",
|
|
34
|
+
"max_value_eth",
|
|
35
|
+
"avg_gas_used",
|
|
36
|
+
"unique_recipients",
|
|
37
|
+
"unique_methods",
|
|
38
|
+
"time_span_hours",
|
|
39
|
+
"avg_interval_seconds",
|
|
40
|
+
"min_interval_seconds",
|
|
41
|
+
"contract_interaction_pct",
|
|
42
|
+
"self_transfer_pct",
|
|
43
|
+
"high_value_tx_pct",
|
|
44
|
+
"failed_tx_pct",
|
|
45
|
+
"token_diversity",
|
|
46
|
+
]
|
|
47
|
+
|
|
48
|
+
|
|
49
|
+
class WalletClassifier:
|
|
50
|
+
def __init__(self):
|
|
51
|
+
self.version = "0.1.0"
|
|
52
|
+
self.is_loaded = False
|
|
53
|
+
self.last_trained: str | None = None
|
|
54
|
+
self.accuracy: float | None = None
|
|
55
|
+
self.model = None
|
|
56
|
+
|
|
57
|
+
def load(self):
|
|
58
|
+
"""Load trained model or use heuristic."""
|
|
59
|
+
model_path = MODEL_DIR / "wallet_classifier.joblib"
|
|
60
|
+
if model_path.exists():
|
|
61
|
+
try:
|
|
62
|
+
import joblib
|
|
63
|
+
|
|
64
|
+
self.model = joblib.load(model_path)
|
|
65
|
+
self.is_loaded = True
|
|
66
|
+
self.last_trained = str(model_path.stat().st_mtime)
|
|
67
|
+
except Exception:
|
|
68
|
+
self._init_heuristic()
|
|
69
|
+
else:
|
|
70
|
+
self._init_heuristic()
|
|
71
|
+
|
|
72
|
+
def _init_heuristic(self):
|
|
73
|
+
self.is_loaded = True
|
|
74
|
+
self.version = "0.1.0-heuristic"
|
|
75
|
+
|
|
76
|
+
def classify(self, features: dict) -> dict:
|
|
77
|
+
"""Classify wallet behavior.
|
|
78
|
+
|
|
79
|
+
Args:
|
|
80
|
+
features: Aggregated transaction features for a wallet.
|
|
81
|
+
|
|
82
|
+
Returns:
|
|
83
|
+
dict with: behavior_type, confidence, risk_score,
|
|
84
|
+
secondary_type, indicators, model
|
|
85
|
+
"""
|
|
86
|
+
if self.model is not None:
|
|
87
|
+
return self._classify_model(features)
|
|
88
|
+
return self._classify_heuristic(features)
|
|
89
|
+
|
|
90
|
+
def _classify_model(self, features: dict) -> dict:
|
|
91
|
+
"""Run inference through trained classifier."""
|
|
92
|
+
X = np.array([[features.get(k, 0) for k in AGG_FEATURE_KEYS]])
|
|
93
|
+
proba = self.model.predict_proba(X)[0]
|
|
94
|
+
classes = list(self.model.classes_)
|
|
95
|
+
|
|
96
|
+
idx = int(np.argmax(proba))
|
|
97
|
+
primary = classes[idx]
|
|
98
|
+
confidence = float(proba[idx])
|
|
99
|
+
|
|
100
|
+
# Second most likely
|
|
101
|
+
sorted_idx = np.argsort(proba)[::-1]
|
|
102
|
+
secondary = classes[sorted_idx[1]] if len(sorted_idx) > 1 else None
|
|
103
|
+
|
|
104
|
+
risk_map = {
|
|
105
|
+
"normal_trader": 0.1,
|
|
106
|
+
"whale": 0.3,
|
|
107
|
+
"bot": 0.4,
|
|
108
|
+
"sniper": 0.6,
|
|
109
|
+
"mev_bot": 0.5,
|
|
110
|
+
"mixer_user": 0.8,
|
|
111
|
+
"rug_deployer": 0.95,
|
|
112
|
+
}
|
|
113
|
+
|
|
114
|
+
return {
|
|
115
|
+
"behavior_type": primary,
|
|
116
|
+
"confidence": round(confidence, 3),
|
|
117
|
+
"risk_score": risk_map.get(primary, 0.5),
|
|
118
|
+
"secondary_type": secondary,
|
|
119
|
+
"indicators": self._extract_indicators(features, primary),
|
|
120
|
+
"model": f"wallet-classifier-{self.version}",
|
|
121
|
+
}
|
|
122
|
+
|
|
123
|
+
def _classify_heuristic(self, features: dict) -> dict:
|
|
124
|
+
"""Rule-based wallet classification until model is trained."""
|
|
125
|
+
tx_count = features.get("tx_count", 0)
|
|
126
|
+
avg_value = features.get("avg_value_eth", 0)
|
|
127
|
+
max_value = features.get("max_value_eth", 0)
|
|
128
|
+
avg_gas = features.get("avg_gas_used", 0)
|
|
129
|
+
unique_recipients = features.get("unique_recipients", 0)
|
|
130
|
+
unique_methods = features.get("unique_methods", 0)
|
|
131
|
+
time_span = features.get("time_span_hours", 0)
|
|
132
|
+
avg_interval = features.get("avg_interval_seconds", 3600)
|
|
133
|
+
min_interval = features.get("min_interval_seconds", 60)
|
|
134
|
+
contract_pct = features.get("contract_interaction_pct", 0)
|
|
135
|
+
self_transfer_pct = features.get("self_transfer_pct", 0)
|
|
136
|
+
high_value_pct = features.get("high_value_tx_pct", 0)
|
|
137
|
+
failed_pct = features.get("failed_tx_pct", 0)
|
|
138
|
+
token_diversity = features.get("token_diversity", 0)
|
|
139
|
+
|
|
140
|
+
scores: dict[str, float] = {t: 0.0 for t in BEHAVIOR_TYPES}
|
|
141
|
+
|
|
142
|
+
# --- Bot detection ---
|
|
143
|
+
if min_interval < 5 and tx_count > 50:
|
|
144
|
+
scores["bot"] += 0.35
|
|
145
|
+
if avg_interval < 30 and tx_count > 100:
|
|
146
|
+
scores["bot"] += 0.25
|
|
147
|
+
if contract_pct > 0.9 and unique_methods <= 3:
|
|
148
|
+
scores["bot"] += 0.20
|
|
149
|
+
|
|
150
|
+
# --- Sniper detection ---
|
|
151
|
+
if min_interval < 3 and avg_gas > 200000:
|
|
152
|
+
scores["sniper"] += 0.30
|
|
153
|
+
if failed_pct > 0.3 and avg_gas > 150000:
|
|
154
|
+
scores["sniper"] += 0.25
|
|
155
|
+
if token_diversity > 20 and time_span < 48:
|
|
156
|
+
scores["sniper"] += 0.20
|
|
157
|
+
|
|
158
|
+
# --- MEV bot detection ---
|
|
159
|
+
if avg_gas > 300000 and min_interval < 5:
|
|
160
|
+
scores["mev_bot"] += 0.30
|
|
161
|
+
if failed_pct > 0.4 and contract_pct > 0.95:
|
|
162
|
+
scores["mev_bot"] += 0.25
|
|
163
|
+
if self_transfer_pct > 0.1 and avg_gas > 200000:
|
|
164
|
+
scores["mev_bot"] += 0.15
|
|
165
|
+
|
|
166
|
+
# --- Whale detection ---
|
|
167
|
+
if max_value > 100:
|
|
168
|
+
scores["whale"] += 0.35
|
|
169
|
+
if avg_value > 10:
|
|
170
|
+
scores["whale"] += 0.25
|
|
171
|
+
if high_value_pct > 0.3:
|
|
172
|
+
scores["whale"] += 0.20
|
|
173
|
+
|
|
174
|
+
# --- Mixer detection ---
|
|
175
|
+
if self_transfer_pct > 0.3:
|
|
176
|
+
scores["mixer_user"] += 0.25
|
|
177
|
+
if unique_recipients > 50 and avg_value < 1:
|
|
178
|
+
scores["mixer_user"] += 0.20
|
|
179
|
+
if token_diversity <= 2 and unique_recipients > 30:
|
|
180
|
+
scores["mixer_user"] += 0.20
|
|
181
|
+
|
|
182
|
+
# --- Rug deployer detection ---
|
|
183
|
+
if unique_methods > 10 and contract_pct > 0.8 and tx_count < 200:
|
|
184
|
+
scores["rug_deployer"] += 0.25
|
|
185
|
+
if high_value_pct > 0.5 and unique_recipients < 5:
|
|
186
|
+
scores["rug_deployer"] += 0.20
|
|
187
|
+
|
|
188
|
+
# --- Normal trader baseline ---
|
|
189
|
+
scores["normal_trader"] += 0.20
|
|
190
|
+
if 10 < avg_interval < 86400:
|
|
191
|
+
scores["normal_trader"] += 0.10
|
|
192
|
+
if 0.1 < avg_value < 10:
|
|
193
|
+
scores["normal_trader"] += 0.10
|
|
194
|
+
if 0.05 < failed_pct < 0.15:
|
|
195
|
+
scores["normal_trader"] += 0.05
|
|
196
|
+
|
|
197
|
+
# Find top behavior
|
|
198
|
+
sorted_types = sorted(scores.items(), key=lambda x: x[1], reverse=True)
|
|
199
|
+
primary = sorted_types[0][0]
|
|
200
|
+
primary_score = sorted_types[0][1]
|
|
201
|
+
secondary = sorted_types[1][0] if len(sorted_types) > 1 else None
|
|
202
|
+
|
|
203
|
+
# Normalize confidence
|
|
204
|
+
total = sum(s for _, s in sorted_types) or 1.0
|
|
205
|
+
confidence = primary_score / total
|
|
206
|
+
|
|
207
|
+
risk_map = {
|
|
208
|
+
"normal_trader": 0.1,
|
|
209
|
+
"whale": 0.3,
|
|
210
|
+
"bot": 0.4,
|
|
211
|
+
"sniper": 0.6,
|
|
212
|
+
"mev_bot": 0.5,
|
|
213
|
+
"mixer_user": 0.8,
|
|
214
|
+
"rug_deployer": 0.95,
|
|
215
|
+
}
|
|
216
|
+
|
|
217
|
+
return {
|
|
218
|
+
"behavior_type": primary,
|
|
219
|
+
"confidence": round(confidence, 3),
|
|
220
|
+
"risk_score": risk_map.get(primary, 0.5),
|
|
221
|
+
"secondary_type": secondary,
|
|
222
|
+
"indicators": self._extract_indicators(features, primary),
|
|
223
|
+
"model": "wallet-classifier-heuristic",
|
|
224
|
+
}
|
|
225
|
+
|
|
226
|
+
@staticmethod
|
|
227
|
+
def _extract_indicators(features: dict, behavior_type: str) -> list[str]:
|
|
228
|
+
"""Generate human-readable indicators explaining the classification."""
|
|
229
|
+
indicators = []
|
|
230
|
+
tx_count = features.get("tx_count", 0)
|
|
231
|
+
avg_interval = features.get("avg_interval_seconds", 3600)
|
|
232
|
+
min_interval = features.get("min_interval_seconds", 60)
|
|
233
|
+
avg_value = features.get("avg_value_eth", 0)
|
|
234
|
+
max_value = features.get("max_value_eth", 0)
|
|
235
|
+
contract_pct = features.get("contract_interaction_pct", 0)
|
|
236
|
+
failed_pct = features.get("failed_tx_pct", 0)
|
|
237
|
+
avg_gas = features.get("avg_gas_used", 0)
|
|
238
|
+
|
|
239
|
+
if behavior_type == "bot":
|
|
240
|
+
indicators.append(f"High frequency: {tx_count} txs, avg {avg_interval:.0f}s apart")
|
|
241
|
+
if contract_pct > 0.8:
|
|
242
|
+
indicators.append(f"Contract-heavy: {contract_pct*100:.0f}% contract calls")
|
|
243
|
+
elif behavior_type == "sniper":
|
|
244
|
+
indicators.append(f"Sub-second execution: min interval {min_interval:.1f}s")
|
|
245
|
+
indicators.append(f"High gas: avg {avg_gas:,.0f} gas per tx")
|
|
246
|
+
if failed_pct > 0.2:
|
|
247
|
+
indicators.append(f"High failure rate: {failed_pct*100:.0f}% failed txs")
|
|
248
|
+
elif behavior_type == "whale":
|
|
249
|
+
indicators.append(f"Large values: avg {avg_value:.2f} ETH, max {max_value:.2f} ETH")
|
|
250
|
+
elif behavior_type == "mev_bot":
|
|
251
|
+
indicators.append(f"MEV patterns: high gas ({avg_gas:,.0f}), rapid execution")
|
|
252
|
+
if failed_pct > 0.3:
|
|
253
|
+
indicators.append(f"Many reverts: {failed_pct*100:.0f}% failure rate")
|
|
254
|
+
elif behavior_type == "mixer_user":
|
|
255
|
+
indicators.append("Mixing patterns: many small transfers to unique addresses")
|
|
256
|
+
elif behavior_type == "rug_deployer":
|
|
257
|
+
indicators.append("Deploy-and-drain pattern detected")
|
|
258
|
+
else:
|
|
259
|
+
indicators.append(f"Normal activity: {tx_count} transactions over time")
|
|
260
|
+
|
|
261
|
+
return indicators
|