@intentsolutionsio/flash-loan-simulator 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 +411 -0
- package/agents/flashloan-agent.md +261 -0
- package/package.json +43 -0
- package/skills/simulating-flash-loans/ARD.md +454 -0
- package/skills/simulating-flash-loans/PRD.md +239 -0
- package/skills/simulating-flash-loans/SKILL.md +109 -0
- package/skills/simulating-flash-loans/config/settings.yaml +241 -0
- package/skills/simulating-flash-loans/references/errors.md +297 -0
- package/skills/simulating-flash-loans/references/examples.md +532 -0
- package/skills/simulating-flash-loans/references/implementation.md +73 -0
- package/skills/simulating-flash-loans/scripts/flash_simulator.py +492 -0
- package/skills/simulating-flash-loans/scripts/formatters.py +512 -0
- package/skills/simulating-flash-loans/scripts/profit_calculator.py +315 -0
- package/skills/simulating-flash-loans/scripts/protocol_adapters.py +416 -0
- package/skills/simulating-flash-loans/scripts/risk_assessor.py +439 -0
- package/skills/simulating-flash-loans/scripts/strategy_engine.py +596 -0
|
@@ -0,0 +1,439 @@
|
|
|
1
|
+
#!/usr/bin/env python3
|
|
2
|
+
"""
|
|
3
|
+
Flash loan risk assessment engine.
|
|
4
|
+
|
|
5
|
+
Evaluates risks for flash loan strategies:
|
|
6
|
+
- MEV competition
|
|
7
|
+
- Execution risk
|
|
8
|
+
- Protocol risk
|
|
9
|
+
- Liquidity risk
|
|
10
|
+
"""
|
|
11
|
+
|
|
12
|
+
from dataclasses import dataclass
|
|
13
|
+
from decimal import Decimal
|
|
14
|
+
from enum import Enum
|
|
15
|
+
from typing import List, Optional
|
|
16
|
+
|
|
17
|
+
from strategy_engine import StrategyResult, StrategyType
|
|
18
|
+
|
|
19
|
+
|
|
20
|
+
class RiskLevel(Enum):
|
|
21
|
+
"""Risk level classification."""
|
|
22
|
+
|
|
23
|
+
LOW = "LOW"
|
|
24
|
+
MEDIUM = "MEDIUM"
|
|
25
|
+
HIGH = "HIGH"
|
|
26
|
+
CRITICAL = "CRITICAL"
|
|
27
|
+
|
|
28
|
+
|
|
29
|
+
@dataclass
|
|
30
|
+
class RiskFactor:
|
|
31
|
+
"""Individual risk factor assessment."""
|
|
32
|
+
|
|
33
|
+
name: str
|
|
34
|
+
level: RiskLevel
|
|
35
|
+
score: float # 0-100, higher = more risky
|
|
36
|
+
description: str
|
|
37
|
+
mitigation: str
|
|
38
|
+
|
|
39
|
+
|
|
40
|
+
@dataclass
|
|
41
|
+
class RiskAssessment:
|
|
42
|
+
"""Complete risk assessment for a strategy."""
|
|
43
|
+
|
|
44
|
+
overall_level: RiskLevel
|
|
45
|
+
overall_score: float
|
|
46
|
+
viability: str # "GO", "CAUTION", "NO-GO"
|
|
47
|
+
factors: List[RiskFactor]
|
|
48
|
+
recommendations: List[str]
|
|
49
|
+
warnings: List[str]
|
|
50
|
+
|
|
51
|
+
|
|
52
|
+
class RiskAssessor:
|
|
53
|
+
"""
|
|
54
|
+
Assesses risks for flash loan strategies.
|
|
55
|
+
|
|
56
|
+
Factors evaluated:
|
|
57
|
+
- MEV Competition: Bot activity on the pair/strategy
|
|
58
|
+
- Execution Risk: Slippage, timing, gas volatility
|
|
59
|
+
- Protocol Risk: Smart contract and oracle risks
|
|
60
|
+
- Liquidity Risk: Pool depth and availability
|
|
61
|
+
"""
|
|
62
|
+
|
|
63
|
+
# MEV risk by token pair (common pairs = more competition)
|
|
64
|
+
MEV_COMPETITION = {
|
|
65
|
+
("ETH", "USDC"): 90,
|
|
66
|
+
("ETH", "USDT"): 85,
|
|
67
|
+
("WBTC", "ETH"): 80,
|
|
68
|
+
("ETH", "DAI"): 75,
|
|
69
|
+
"default": 50,
|
|
70
|
+
}
|
|
71
|
+
|
|
72
|
+
# Protocol risk scores (higher = riskier)
|
|
73
|
+
PROTOCOL_RISK = {
|
|
74
|
+
"aave": 15, # Well-audited, proven
|
|
75
|
+
"compound": 20,
|
|
76
|
+
"dydx": 25,
|
|
77
|
+
"balancer": 30,
|
|
78
|
+
"uniswap": 20,
|
|
79
|
+
"sushiswap": 35,
|
|
80
|
+
"curve": 25,
|
|
81
|
+
"default": 50,
|
|
82
|
+
}
|
|
83
|
+
|
|
84
|
+
def __init__(self, eth_price_usd: float = 2500.0):
|
|
85
|
+
"""Initialize assessor."""
|
|
86
|
+
self.eth_price_usd = eth_price_usd
|
|
87
|
+
|
|
88
|
+
def assess(self, result: StrategyResult) -> RiskAssessment:
|
|
89
|
+
"""
|
|
90
|
+
Perform complete risk assessment.
|
|
91
|
+
|
|
92
|
+
Args:
|
|
93
|
+
result: Strategy simulation result
|
|
94
|
+
|
|
95
|
+
Returns:
|
|
96
|
+
Complete risk assessment
|
|
97
|
+
"""
|
|
98
|
+
factors = []
|
|
99
|
+
recommendations = []
|
|
100
|
+
warnings = []
|
|
101
|
+
|
|
102
|
+
# 1. MEV Competition Risk
|
|
103
|
+
mev_factor = self._assess_mev_risk(result)
|
|
104
|
+
factors.append(mev_factor)
|
|
105
|
+
|
|
106
|
+
# 2. Execution Risk
|
|
107
|
+
exec_factor = self._assess_execution_risk(result)
|
|
108
|
+
factors.append(exec_factor)
|
|
109
|
+
|
|
110
|
+
# 3. Protocol Risk
|
|
111
|
+
protocol_factor = self._assess_protocol_risk(result)
|
|
112
|
+
factors.append(protocol_factor)
|
|
113
|
+
|
|
114
|
+
# 4. Liquidity Risk
|
|
115
|
+
liquidity_factor = self._assess_liquidity_risk(result)
|
|
116
|
+
factors.append(liquidity_factor)
|
|
117
|
+
|
|
118
|
+
# 5. Profitability Risk
|
|
119
|
+
profit_factor = self._assess_profit_risk(result)
|
|
120
|
+
factors.append(profit_factor)
|
|
121
|
+
|
|
122
|
+
# Calculate overall score (weighted average)
|
|
123
|
+
weights = {
|
|
124
|
+
"MEV Competition": 0.30,
|
|
125
|
+
"Execution Risk": 0.25,
|
|
126
|
+
"Protocol Risk": 0.15,
|
|
127
|
+
"Liquidity Risk": 0.15,
|
|
128
|
+
"Profit Margin": 0.15,
|
|
129
|
+
}
|
|
130
|
+
|
|
131
|
+
overall_score = sum(
|
|
132
|
+
f.score * weights.get(f.name, 0.2)
|
|
133
|
+
for f in factors
|
|
134
|
+
)
|
|
135
|
+
|
|
136
|
+
# Determine overall level
|
|
137
|
+
if overall_score < 30:
|
|
138
|
+
overall_level = RiskLevel.LOW
|
|
139
|
+
viability = "GO"
|
|
140
|
+
elif overall_score < 50:
|
|
141
|
+
overall_level = RiskLevel.MEDIUM
|
|
142
|
+
viability = "CAUTION"
|
|
143
|
+
elif overall_score < 70:
|
|
144
|
+
overall_level = RiskLevel.HIGH
|
|
145
|
+
viability = "CAUTION"
|
|
146
|
+
else:
|
|
147
|
+
overall_level = RiskLevel.CRITICAL
|
|
148
|
+
viability = "NO-GO"
|
|
149
|
+
|
|
150
|
+
# Generate recommendations
|
|
151
|
+
recommendations = self._generate_recommendations(factors, result)
|
|
152
|
+
|
|
153
|
+
# Generate warnings
|
|
154
|
+
for factor in factors:
|
|
155
|
+
if factor.level in [RiskLevel.HIGH, RiskLevel.CRITICAL]:
|
|
156
|
+
warnings.append(f"{factor.name}: {factor.description}")
|
|
157
|
+
|
|
158
|
+
return RiskAssessment(
|
|
159
|
+
overall_level=overall_level,
|
|
160
|
+
overall_score=overall_score,
|
|
161
|
+
viability=viability,
|
|
162
|
+
factors=factors,
|
|
163
|
+
recommendations=recommendations,
|
|
164
|
+
warnings=warnings,
|
|
165
|
+
)
|
|
166
|
+
|
|
167
|
+
def _assess_mev_risk(self, result: StrategyResult) -> RiskFactor:
|
|
168
|
+
"""Assess MEV competition risk."""
|
|
169
|
+
# Higher score = more competition
|
|
170
|
+
pair_key = (result.loan_asset, "USDC") # Simplified
|
|
171
|
+
|
|
172
|
+
score = self.MEV_COMPETITION.get(
|
|
173
|
+
pair_key,
|
|
174
|
+
self.MEV_COMPETITION.get("default", 50)
|
|
175
|
+
)
|
|
176
|
+
|
|
177
|
+
# Adjust for trade size (larger = more attractive to MEV)
|
|
178
|
+
if result.loan_amount > 100:
|
|
179
|
+
score = min(100, score + 15)
|
|
180
|
+
elif result.loan_amount > 50:
|
|
181
|
+
score = min(100, score + 10)
|
|
182
|
+
|
|
183
|
+
# Adjust for profit (more profit = more competition)
|
|
184
|
+
if result.net_profit_usd > 500:
|
|
185
|
+
score = min(100, score + 10)
|
|
186
|
+
|
|
187
|
+
if score < 30:
|
|
188
|
+
level = RiskLevel.LOW
|
|
189
|
+
elif score < 50:
|
|
190
|
+
level = RiskLevel.MEDIUM
|
|
191
|
+
elif score < 70:
|
|
192
|
+
level = RiskLevel.HIGH
|
|
193
|
+
else:
|
|
194
|
+
level = RiskLevel.CRITICAL
|
|
195
|
+
|
|
196
|
+
return RiskFactor(
|
|
197
|
+
name="MEV Competition",
|
|
198
|
+
level=level,
|
|
199
|
+
score=score,
|
|
200
|
+
description=f"High bot activity on {result.loan_asset} pairs",
|
|
201
|
+
mitigation="Use Flashbots Protect or private transactions",
|
|
202
|
+
)
|
|
203
|
+
|
|
204
|
+
def _assess_execution_risk(self, result: StrategyResult) -> RiskFactor:
|
|
205
|
+
"""Assess execution risk (slippage, timing)."""
|
|
206
|
+
# Base score from number of steps
|
|
207
|
+
num_steps = len(result.steps)
|
|
208
|
+
score = min(100, num_steps * 15)
|
|
209
|
+
|
|
210
|
+
# Adjust for gas cost relative to profit
|
|
211
|
+
if result.net_profit > 0:
|
|
212
|
+
gas_ratio = float(result.gas_cost_eth / result.net_profit)
|
|
213
|
+
if gas_ratio > 0.5:
|
|
214
|
+
score = min(100, score + 20)
|
|
215
|
+
elif gas_ratio > 0.3:
|
|
216
|
+
score = min(100, score + 10)
|
|
217
|
+
|
|
218
|
+
if score < 30:
|
|
219
|
+
level = RiskLevel.LOW
|
|
220
|
+
elif score < 50:
|
|
221
|
+
level = RiskLevel.MEDIUM
|
|
222
|
+
elif score < 70:
|
|
223
|
+
level = RiskLevel.HIGH
|
|
224
|
+
else:
|
|
225
|
+
level = RiskLevel.CRITICAL
|
|
226
|
+
|
|
227
|
+
return RiskFactor(
|
|
228
|
+
name="Execution Risk",
|
|
229
|
+
level=level,
|
|
230
|
+
score=score,
|
|
231
|
+
description=f"{num_steps} steps with gas-sensitive timing",
|
|
232
|
+
mitigation="Set tight slippage tolerance; use gas price oracles",
|
|
233
|
+
)
|
|
234
|
+
|
|
235
|
+
def _assess_protocol_risk(self, result: StrategyResult) -> RiskFactor:
|
|
236
|
+
"""Assess protocol/smart contract risk."""
|
|
237
|
+
# Get protocols involved
|
|
238
|
+
protocols = set()
|
|
239
|
+
for step in result.steps:
|
|
240
|
+
protocols.add(step.protocol.lower())
|
|
241
|
+
|
|
242
|
+
# Sum risk scores
|
|
243
|
+
total_risk = sum(
|
|
244
|
+
self.PROTOCOL_RISK.get(p, self.PROTOCOL_RISK["default"])
|
|
245
|
+
for p in protocols
|
|
246
|
+
)
|
|
247
|
+
|
|
248
|
+
# Average across protocols
|
|
249
|
+
score = total_risk / len(protocols) if protocols else 50
|
|
250
|
+
|
|
251
|
+
if score < 25:
|
|
252
|
+
level = RiskLevel.LOW
|
|
253
|
+
elif score < 40:
|
|
254
|
+
level = RiskLevel.MEDIUM
|
|
255
|
+
elif score < 60:
|
|
256
|
+
level = RiskLevel.HIGH
|
|
257
|
+
else:
|
|
258
|
+
level = RiskLevel.CRITICAL
|
|
259
|
+
|
|
260
|
+
return RiskFactor(
|
|
261
|
+
name="Protocol Risk",
|
|
262
|
+
level=level,
|
|
263
|
+
score=score,
|
|
264
|
+
description=f"Using {len(protocols)} protocol(s): {', '.join(protocols)}",
|
|
265
|
+
mitigation="Verify protocol audits; monitor for exploits",
|
|
266
|
+
)
|
|
267
|
+
|
|
268
|
+
def _assess_liquidity_risk(self, result: StrategyResult) -> RiskFactor:
|
|
269
|
+
"""Assess liquidity/slippage risk."""
|
|
270
|
+
# Simplified: larger trades = higher liquidity risk
|
|
271
|
+
trade_usd = float(result.loan_amount * Decimal(str(self.eth_price_usd)))
|
|
272
|
+
|
|
273
|
+
if trade_usd < 10000:
|
|
274
|
+
score = 20
|
|
275
|
+
elif trade_usd < 50000:
|
|
276
|
+
score = 35
|
|
277
|
+
elif trade_usd < 100000:
|
|
278
|
+
score = 50
|
|
279
|
+
elif trade_usd < 500000:
|
|
280
|
+
score = 70
|
|
281
|
+
else:
|
|
282
|
+
score = 90
|
|
283
|
+
|
|
284
|
+
if score < 30:
|
|
285
|
+
level = RiskLevel.LOW
|
|
286
|
+
elif score < 50:
|
|
287
|
+
level = RiskLevel.MEDIUM
|
|
288
|
+
elif score < 70:
|
|
289
|
+
level = RiskLevel.HIGH
|
|
290
|
+
else:
|
|
291
|
+
level = RiskLevel.CRITICAL
|
|
292
|
+
|
|
293
|
+
return RiskFactor(
|
|
294
|
+
name="Liquidity Risk",
|
|
295
|
+
level=level,
|
|
296
|
+
score=score,
|
|
297
|
+
description=f"${trade_usd:,.0f} trade may cause significant slippage",
|
|
298
|
+
mitigation="Check pool liquidity; consider splitting order",
|
|
299
|
+
)
|
|
300
|
+
|
|
301
|
+
def _assess_profit_risk(self, result: StrategyResult) -> RiskFactor:
|
|
302
|
+
"""Assess profit margin risk."""
|
|
303
|
+
# Thin margins = high risk
|
|
304
|
+
if result.loan_amount > 0:
|
|
305
|
+
margin_pct = float(result.net_profit / result.loan_amount * 100)
|
|
306
|
+
else:
|
|
307
|
+
margin_pct = 0
|
|
308
|
+
|
|
309
|
+
if margin_pct < 0:
|
|
310
|
+
score = 100 # Negative profit = maximum risk
|
|
311
|
+
elif margin_pct < 0.1:
|
|
312
|
+
score = 80
|
|
313
|
+
elif margin_pct < 0.5:
|
|
314
|
+
score = 50
|
|
315
|
+
elif margin_pct < 1.0:
|
|
316
|
+
score = 30
|
|
317
|
+
else:
|
|
318
|
+
score = 15
|
|
319
|
+
|
|
320
|
+
if score < 30:
|
|
321
|
+
level = RiskLevel.LOW
|
|
322
|
+
elif score < 50:
|
|
323
|
+
level = RiskLevel.MEDIUM
|
|
324
|
+
elif score < 70:
|
|
325
|
+
level = RiskLevel.HIGH
|
|
326
|
+
else:
|
|
327
|
+
level = RiskLevel.CRITICAL
|
|
328
|
+
|
|
329
|
+
return RiskFactor(
|
|
330
|
+
name="Profit Margin",
|
|
331
|
+
level=level,
|
|
332
|
+
score=score,
|
|
333
|
+
description=f"{margin_pct:.3f}% profit margin {'(NEGATIVE!)' if margin_pct < 0 else ''}",
|
|
334
|
+
mitigation="Increase trade size or wait for better opportunity",
|
|
335
|
+
)
|
|
336
|
+
|
|
337
|
+
def _generate_recommendations(
|
|
338
|
+
self, factors: List[RiskFactor], result: StrategyResult
|
|
339
|
+
) -> List[str]:
|
|
340
|
+
"""Generate actionable recommendations."""
|
|
341
|
+
recs = []
|
|
342
|
+
|
|
343
|
+
# Check MEV risk
|
|
344
|
+
mev_factor = next((f for f in factors if f.name == "MEV Competition"), None)
|
|
345
|
+
if mev_factor and mev_factor.level in [RiskLevel.HIGH, RiskLevel.CRITICAL]:
|
|
346
|
+
recs.append("Use Flashbots Protect to avoid sandwich attacks")
|
|
347
|
+
recs.append("Consider private transaction submission")
|
|
348
|
+
|
|
349
|
+
# Check execution risk
|
|
350
|
+
exec_factor = next((f for f in factors if f.name == "Execution Risk"), None)
|
|
351
|
+
if exec_factor and exec_factor.level in [RiskLevel.HIGH, RiskLevel.CRITICAL]:
|
|
352
|
+
recs.append("Set tight slippage tolerance (0.5% or less)")
|
|
353
|
+
recs.append("Monitor gas prices before execution")
|
|
354
|
+
|
|
355
|
+
# Check profit margin
|
|
356
|
+
profit_factor = next((f for f in factors if f.name == "Profit Margin"), None)
|
|
357
|
+
if profit_factor and profit_factor.level == RiskLevel.CRITICAL:
|
|
358
|
+
if result.net_profit < 0:
|
|
359
|
+
recs.append("Strategy is UNPROFITABLE - do not execute")
|
|
360
|
+
else:
|
|
361
|
+
recs.append("Profit margin too thin - wait for better opportunity")
|
|
362
|
+
|
|
363
|
+
# Check liquidity
|
|
364
|
+
liq_factor = next((f for f in factors if f.name == "Liquidity Risk"), None)
|
|
365
|
+
if liq_factor and liq_factor.level in [RiskLevel.HIGH, RiskLevel.CRITICAL]:
|
|
366
|
+
recs.append("Consider splitting into smaller trades")
|
|
367
|
+
recs.append("Check DEX liquidity depth before execution")
|
|
368
|
+
|
|
369
|
+
# General recommendations
|
|
370
|
+
if not recs:
|
|
371
|
+
recs.append("Conditions appear favorable - proceed with caution")
|
|
372
|
+
recs.append("Always test on testnet first")
|
|
373
|
+
|
|
374
|
+
return recs
|
|
375
|
+
|
|
376
|
+
|
|
377
|
+
def demo():
|
|
378
|
+
"""Demonstrate risk assessment."""
|
|
379
|
+
from strategy_engine import StrategyFactory, StrategyType, ArbitrageParams
|
|
380
|
+
|
|
381
|
+
# Run a simulation first
|
|
382
|
+
factory = StrategyFactory()
|
|
383
|
+
strategy = factory.create(StrategyType.SIMPLE_ARBITRAGE)
|
|
384
|
+
|
|
385
|
+
params = ArbitrageParams(
|
|
386
|
+
input_token="ETH",
|
|
387
|
+
output_token="USDC",
|
|
388
|
+
amount=Decimal("100"),
|
|
389
|
+
dex_buy="sushiswap",
|
|
390
|
+
dex_sell="uniswap",
|
|
391
|
+
provider="aave",
|
|
392
|
+
)
|
|
393
|
+
|
|
394
|
+
result = strategy.simulate(params)
|
|
395
|
+
|
|
396
|
+
# Assess risk
|
|
397
|
+
assessor = RiskAssessor(eth_price_usd=2500.0)
|
|
398
|
+
assessment = assessor.assess(result)
|
|
399
|
+
|
|
400
|
+
print("=" * 60)
|
|
401
|
+
print("RISK ASSESSMENT")
|
|
402
|
+
print("=" * 60)
|
|
403
|
+
|
|
404
|
+
print(f"\nOverall Risk: {assessment.overall_level.value}")
|
|
405
|
+
print(f"Risk Score: {assessment.overall_score:.0f}/100")
|
|
406
|
+
print(f"Viability: {assessment.viability}")
|
|
407
|
+
|
|
408
|
+
print("\n" + "-" * 60)
|
|
409
|
+
print("RISK FACTORS")
|
|
410
|
+
print("-" * 60)
|
|
411
|
+
|
|
412
|
+
for factor in assessment.factors:
|
|
413
|
+
indicator = {
|
|
414
|
+
RiskLevel.LOW: "🟢",
|
|
415
|
+
RiskLevel.MEDIUM: "🟡",
|
|
416
|
+
RiskLevel.HIGH: "🟠",
|
|
417
|
+
RiskLevel.CRITICAL: "🔴",
|
|
418
|
+
}.get(factor.level, "⚪")
|
|
419
|
+
|
|
420
|
+
print(f"\n{indicator} {factor.name}: {factor.level.value} ({factor.score:.0f})")
|
|
421
|
+
print(f" {factor.description}")
|
|
422
|
+
print(f" → {factor.mitigation}")
|
|
423
|
+
|
|
424
|
+
if assessment.warnings:
|
|
425
|
+
print("\n" + "-" * 60)
|
|
426
|
+
print("WARNINGS")
|
|
427
|
+
print("-" * 60)
|
|
428
|
+
for warning in assessment.warnings:
|
|
429
|
+
print(f" ⚠️ {warning}")
|
|
430
|
+
|
|
431
|
+
print("\n" + "-" * 60)
|
|
432
|
+
print("RECOMMENDATIONS")
|
|
433
|
+
print("-" * 60)
|
|
434
|
+
for rec in assessment.recommendations:
|
|
435
|
+
print(f" • {rec}")
|
|
436
|
+
|
|
437
|
+
|
|
438
|
+
if __name__ == "__main__":
|
|
439
|
+
demo()
|