Independent on-chain risk intelligence engine for Flash Trade perpetuals on Solana.
Live Dashboard: dashboard-ebon-sigma-94.vercel.app
Repository: github.com/Abdr007/flash-risk-engine
Flash Risk Engine reads all public on-chain state from the Flash Trade perpetual exchange program and computes real-time risk metrics for every open position. It operates entirely from publicly available data: program accounts fetched via RPC, oracle prices read from on-chain accounts, and account layouts defined in the published IDL.
The engine does not rely on internal APIs, private data feeds, or proprietary infrastructure. Every input is verifiable on-chain. Every formula references its Rust source.
This is an independent verification tool. It is not affiliated with Flash Trade.
- Reads all pool, custody, market, position, and oracle accounts from Solana mainnet
- Computes per-position PnL, margin, leverage, liquidation price, and distance-to-liquidation
- Detects positions that exceed the maximum leverage threshold (liquidation condition)
- Models borrow rates using the two-slope kinked utilization model
- Aggregates protocol-wide risk metrics: liquidation density, leverage distribution, utilization
- Execute transactions or liquidations
- Access closed-source production logic
- Predict future price movements
- Serve as a trading signal
Live leverage computation -- Per-position leverage calculated as size_usd * BPS_POWER / margin_usd, matching the on-chain formula from pool.rs.
Liquidation condition detection -- Identifies positions where current_leverage > max_leverage, the exact condition that triggers the liquidate instruction.
Borrow rate modeling -- Implements the two-slope kinked utilization model from custody.rs, with per-custody base rate, slope1, slope2, and optimal utilization.
Utilization monitoring -- Per-custody utilization computed as locked / owned in native token units, with current rate and distance-to-optimal.
Liquidation density buckets -- Positions bucketed by percentage distance to liquidation: <0.5%, 0.5-1%, 1-2%, 2-5%, 5-10%, >10%.
Leverage distribution histogram -- Open interest bucketed by leverage: 1-2x, 2-5x, 5-8x, 8-12x, 12-20x, 20-50x, 50x+.
Audit mode (--audit) -- Structured verification snapshot outputting raw, scaled, and human-readable values for every computed quantity, with SHA-256 hash of the IDL used.
Post-run sanity checks -- Automated invariant checks: OI summation consistency, bucket totals, utilization cross-validation, borrow rate model verification.
Trace mode (--trace <address>) -- Full diagnostic trace for a single position. Outputs raw decoded fields, oracle values, custody parameters, borrow state, and all computed risk values including data integrity classification.
Data report (--data-report) -- Outputs program ID, RPC endpoint statistics, account decode success rates, failure counts by category, oracle staleness counts, and data integrity classification summary.
All formulas below are implemented with arbitrary-precision integer arithmetic (BN.js) to match on-chain Rust behavior. No floating-point is used in intermediate computations. See docs/mathematical-model.md for full derivations and worked examples.
leverage = size_usd * 10,000 / margin_usd
Result is in BPS (basis points, 10^4). A leverage of 10x = 100,000 BPS.
margin_usd = collateral_usd + profit_usd - loss_usd
Clamped to max(0, result). All values in USD_DECIMALS (10^6).
exit_price = pessimistic_exit(oracle_price, side, spread)
Long:
profit = size_usd * max(0, exit_price - entry_price) / entry_price
loss = size_usd * max(0, entry_price - exit_price) / entry_price
Short:
profit = size_usd * max(0, entry_price - exit_price) / entry_price
loss = size_usd * max(0, exit_price - entry_price) / entry_price
total_loss = price_loss + interest_usd + close_fee_usd
profit = min(profit, locked_value_usd) // capped at locked collateral
liquidatable = (current_leverage > max_leverage)
Where max_leverage is from custody.pricing.maxLeverage (in BPS).
min_margin = size_usd * BPS_POWER / max_leverage
buffer = collateral_usd - interest_usd - close_fee_usd - min_margin
Long: liq_price = entry_price * (size_usd - buffer) / size_usd
Short: liq_price = entry_price * (size_usd + buffer) / size_usd
if utilization < optimal_utilization:
rate = base_rate + (utilization / optimal_utilization) * slope1
else:
rate = base_rate + slope1
+ ((utilization - optimal) / (RATE_POWER - optimal)) * slope2
All parameters in RATE_DECIMALS (10^9). Rates are per-hour. Maximum rate is base_rate + slope1 + slope2.
increment = ceil(time_delta_seconds * current_rate / 3600)
cumulative += increment
interest_usd = (cumulative_now - position_snapshot) * locked_usd / RATE_POWER
Ceiling division ensures borrowers are never undercharged.
| Quantity | Scale | Unit |
|---|---|---|
| USD amounts (size, collateral, margin, PnL) | 10^6 | USD_DECIMALS |
| Prices (entry, exit, oracle) | 10^9 | ORACLE_PRICE_SCALE |
| Leverage, fee ratios | 10^4 | BPS_DECIMALS |
| Borrow rates, utilization | 10^9 | RATE_DECIMALS |
| Trade spreads | 10^4 * 100 | 100ths of BPS |
| Source | Description |
|---|---|
Flash Trade IDL (perpetuals.json) |
Account layouts, type definitions, discriminators. v15.1.0. |
| On-chain program accounts | Pool, Custody, Market, Position accounts fetched via getProgramAccounts |
| Custom Oracle accounts | Per-custody spot price, EMA, confidence interval, exponent, publish time |
| PoolConfig.json | Mainnet pool addresses (8 pools). Embedded in src/pool-config.ts |
| Reference implementation | flash-perpetuals open-source Rust programs. Formula derivation source |
| Rust SDK | flash-sdk-rust reader functions and state structures |
| Component | Status | Source |
|---|---|---|
| Program ID | Verified | Cross-checked from IDL, PoolConfig.json, Rust SDK declare_id! |
| IDL account layouts | Verified | perpetuals.json v15.1.0 |
| Precision constants (BPS, RATE, USD) | Verified | Matched between Rust SDK and TS SDK |
| Oracle price reading | Verified | Custom oracle with divergence banding, from flash-sdk-rust |
| PnL computation | Verified | Matches pool.rs get_pnl_usd |
| Leverage computation | Verified | Matches pool.rs get_leverage |
| Liquidation condition | Verified | leverage > max_leverage from reference impl |
| Borrow rate (kink model) | Verified | Matches custody.rs update_borrow_rate |
| Lock fee accumulation | Verified | Ceiling division matches reference impl |
| Close fee computation | Verified | size_usd * close_position_fee / RATE_POWER |
| Liquidation price | Partial | Derived from liquidation condition; static fee estimate |
price_impact_usd handling |
Not verified | Production source closed |
degen_size_usd leverage thresholds |
Not verified | Production source closed |
unsettled_value_usd settlement |
Not verified | Production source closed |
unsettled_fees_usd settlement |
Not verified | Production source closed |
| Lazer oracle integration | Not verified | Production source closed |
See docs/verification.md for detailed verification methodology.
npm installDownloads the canonical IDL from the official Flash Trade SDK repository:
npm run setup# Default (uses public Solana RPC)
npm start
# With custom RPC endpoint
RPC_URL=https://your-rpc-endpoint.com npm start
# With explicit RPC flag
npm start -- --rpc https://your-rpc-endpoint.comOutputs a structured verification snapshot with raw, scaled, and human-readable values:
npm start -- --auditFull diagnostic trace for a single position (or highest-leverage position if no address given):
# Trace a specific position
npm start -- --trace <positionAddress>
# Trace highest-leverage position (auto-selected)
npm start -- --traceOutputs RPC statistics, decode success rates, and data integrity classification:
npm start -- --data-reportLive: dashboard-ebon-sigma-94.vercel.app
Web-based visualization console (Next.js):
# Generate snapshot data
npm run snapshot
# Start dashboard (development)
cd dashboard && npm run dev
# Start dashboard (production)
cd dashboard && ./scripts/start-production.shOpen http://localhost:3000. The dashboard reads from dashboard/data/snapshot.json. Re-run npm run snapshot to refresh data.
API endpoints:
GET /api/snapshot— Full snapshot dataGET /api/trace?position=<address>— Position traceGET /api/data-report— RPC stats and data qualityGET /api/health— Health check, snapshot age, memory usagePOST /api/refresh— Protected snapshot regeneration (requiresAuthorization: Bearer <API_SECRET>)
Production deployment guide: dashboard/DEPLOYMENT.md
Export engine output as JSON:
# Default output to dashboard/data/snapshot.json
npm run snapshot
# Custom output path
npm start -- --json /path/to/output.jsonSubscribes to WebSocket account changes for real-time monitoring:
npm start -- --subscribenpm run lintnpm run build Solana Mainnet
|
[RPC / WSS]
|
+----------+----------+
| |
FlashDecoder FlashSubscriber
(decoder.ts) (subscriber.ts)
| |
| Anchor IDL | WebSocket
| Deserialization | Account Changes
| |
+----------+----------+
|
Decoded Accounts
(Pool, Custody, Market,
Position, CustomOracle)
|
+----------+----------+
| | |
risk-engine oracle.ts metrics.ts
| | |
Per-position Price Aggregation
PnL, margin, banding, density maps,
leverage, exit histograms,
liquidation pricing, utilization,
lock fee borrow rate
| | |
+----------+----------+
|
guards.ts
Runtime invariant
assertions
|
cli.ts
Output formatting,
audit mode,
sanity checks,
presentation
I/O Layer (decoder.ts, subscriber.ts) -- Handles all network communication with Solana. Rate-limit-aware batched fetching. Anchor IDL-based deserialization.
Pure Computation Layer (risk-engine.ts, oracle.ts) -- Deterministic, side-effect-free functions. All math uses BN arbitrary-precision integers. Every function documents its Rust source, input units, and output units.
Aggregation Layer (metrics.ts) -- Computes protocol-wide metrics from individual position risk assessments. Bucketing, histograms, summary statistics.
Guard Layer (guards.ts) -- Runtime invariant assertions at critical computation points. Catches impossible states from corrupted data or engine bugs.
Presentation Layer (cli.ts) -- Argument parsing, output formatting, audit mode, sanity checks, and the final risk summary.
See docs/architecture.md for detailed data flow documentation.
The Flash Trade core program (perpetuals-closed.git) is closed-source. All formulas in this engine are derived from the publicly available reference implementation and SDK. The following are known areas of potential divergence:
- Production core program -- Closed-source. May contain logic not present in the reference implementation.
- Degen mode --
degen_size_usdselects betweenmaxLeverageandmaxDegenLeverage. This engine usesmaxLeverageexclusively. price_impact_usd-- Production may adjust entry prices based on market impact. This engine uses storedentryPrice.- Unsettled values --
unsettled_value_usdandunsettled_fees_usdexist on position accounts but settlement logic is not in the reference implementation. - No prediction -- This engine computes current state only. It does not forecast liquidation cascades or price movements.
- Non-atomic snapshot -- On-chain state (pools, custodies, markets, oracles, positions) is fetched sequentially via RPC. Between fetches, on-chain state can change. The engine mitigates this by skipping positions with unresolved references, but computed metrics reflect a best-effort snapshot, not an atomic point-in-time view.
See docs/known-limitations.md for complete documentation.
All computations in this engine are subject to the following constraints:
-
Live mainnet RPC only. Every account value (pool, custody, market, position, oracle) is fetched from Solana mainnet via RPC at runtime. No values are cached between runs.
-
No synthetic data. No hardcoded prices, no default oracle values, no placeholder accounts. If an oracle returns zero, the computation throws. If an account is missing, the position is excluded and logged.
-
No default values assumed. Oracle exponents are read from each custody's oracle account dynamically. Token decimals come from the custody account. Leverage thresholds come from custody pricing parameters. Nothing is inferred or assumed.
-
Invalid or stale data results in exclusion. Each position is classified with a
DataIntegritystatus:valid— All inputs verified from live on-chain data.stale_oracle— OraclepublishTimeexceedsmaxPriceAgeSec. Position is excluded from liquidation computation.incomplete_data— Missing custody, market, or oracle account. Position is skipped entirely.invalid_data— Corrupted or zero oracle price, zero entry price, or unresolvable market side. Position is skipped with error logged.
-
Snapshot is non-atomic. On-chain state is fetched sequentially via RPC. Between fetches, state can change. Computed metrics reflect a best-effort snapshot, not an atomic point-in-time view. Positions with unresolvable cross-references are excluded rather than filled with synthetic values.
-
Failure is explicit. Every skipped position is counted and categorized by reason (
zero_size,missing_market,invalid_side,missing_custody,missing_oracle,computation_error). The--data-reportflag outputs the complete failure breakdown. No failures are silently absorbed.
flash-risk-engine/
├── src/
│ ├── cli.ts Entry point, output formatting, audit mode
│ ├── risk-engine.ts Pure risk computation (PnL, margin, leverage, liquidation)
│ ├── oracle.ts Oracle pricing, exit price, trade spread, lock fee
│ ├── metrics.ts Aggregated metrics (density, histograms, utilization)
│ ├── guards.ts Runtime invariant assertions
│ ├── snapshot-service.ts JSON snapshot generator for dashboard
│ ├── audit.ts Adversarial audit script (standalone deep trace)
│ ├── liquidation-proof.ts Liquidation proof generator (standalone)
│ ├── types.ts TypeScript interfaces matching IDL account layouts
│ ├── constants.ts Verified precision constants with source references
│ ├── decoder.ts Anchor IDL-based account deserialization
│ ├── subscriber.ts WebSocket live update handler
│ ├── pool-config.ts Mainnet pool addresses
│ └── idl/
│ └── perpetuals.json IDL (downloaded via setup script)
├── dashboard/ Web visualization console (Next.js)
│ ├── src/
│ │ ├── app/
│ │ │ ├── page.tsx Main dashboard page
│ │ │ └── api/ REST API routes
│ │ │ ├── snapshot/route.ts GET /api/snapshot
│ │ │ ├── trace/route.ts GET /api/trace?position=
│ │ │ ├── data-report/route.ts GET /api/data-report
│ │ │ ├── health/route.ts GET /api/health
│ │ │ └── refresh/route.ts POST /api/refresh (protected)
│ │ ├── middleware.ts Rate limiting, request logging
│ │ ├── components/ React components
│ │ └── lib/
│ │ ├── types.ts Serializable API types
│ │ └── env.ts Server-side environment validation
│ ├── scripts/
│ │ ├── start-production.sh Production startup script
│ │ ├── refresh-snapshot.sh Cron-compatible snapshot refresh
│ │ ├── load-test.sh Concurrent load simulation
│ │ ├── fre-dashboard.service systemd unit (dashboard server)
│ │ ├── fre-refresh.service systemd unit (snapshot refresh)
│ │ └── fre-refresh.timer systemd timer (5-min interval)
│ ├── data/
│ │ └── snapshot.json Generated snapshot (gitignored)
│ ├── DEPLOYMENT.md Production deployment guide
│ └── .env.production.example Environment variable template
├── docs/
│ ├── architecture.md Data flow and layer separation
│ ├── mathematical-model.md Full derivations and worked examples
│ ├── verification.md Verification methodology and evidence
│ └── known-limitations.md Production divergence documentation
├── scripts/
│ └── setup.sh IDL download and verification
├── package.json
├── tsconfig.json
├── LICENSE
└── CONTRIBUTING.md
Flash Risk Engine is an independent analytical tool. It is not affiliated with, endorsed by, or maintained by Flash Trade or any related entity. The engine reads publicly available on-chain data and applies formulas derived from publicly available source code. It makes no guarantees about the accuracy of its outputs relative to the production Flash Trade program, which is closed-source.
Use at your own risk. This software is provided as-is, without warranty of any kind.
MIT. See LICENSE.