Solana Mainnet (RPC)
|
| getProgramAccounts, getAccountInfo
|
+------v-------+
| FlashDecoder | Account deserialization via Anchor IDL
| decoder.ts | Rate-limit-aware batched fetching
+------+-------+
|
| Typed account structs:
| PoolAccount, CustodyAccount, MarketAccount,
| PositionAccount, CustomOracleAccount
|
+------v-------+ +-------------+
| risk-engine |<----| oracle.ts | Oracle price range, exit pricing,
| .ts | | | trade spread, lock fee computation
| | +-------------+
| Per-position:|
| - PnL | +-------------+
| - Margin |<----| guards.ts | Runtime invariant assertions
| - Leverage | +-------------+
| - Liquidation|
+------+-------+
|
| PositionRisk[]
|
+------v-------+
| metrics.ts | Aggregation:
| | - Liquidation density map
| | - Leverage distribution histogram
| | - Utilization monitoring
| | - Borrow rate forecasting
| | - Risk summary statistics
+------+-------+
|
| RiskSummary, LiquidationBucket[],
| LeverageBucket[], UtilizationInfo
|
+------v-------+
| cli.ts | Output:
| | - Phased terminal output
| | - Audit verification snapshot
| | - Post-run sanity checks
| | - Live snapshot summary
+--------------+
The engine enforces a strict boundary between I/O and computation:
decoder.ts-- RPC calls, Anchor deserialization, rate limitingsubscriber.ts-- WebSocket subscriptions, event callbackscli.ts-- Console output, argument parsing, file system reads (audit mode IDL hash)
risk-engine.ts-- All functions are pure. Given the same inputs, they produce the same outputs. No network calls, no console output, no global state.oracle.ts-- Pure functions for price banding, exit pricing, spread interpolation, lock fee accumulation.metrics.ts-- Pure aggregation overPositionRisk[]arrays.guards.ts-- Pure assertion functions. The only side effect is throwing on violation.
This separation means the computation layer can be tested, audited, and reasoned about independently of the I/O layer.
Guards are integrated at five critical points:
| Guard | Location | Invariant |
|---|---|---|
assertNonNegativeMargin |
computeMargin() |
Margin >= 0 after clamping |
assertNonZeroDivisor |
computeLeverage(), computeExitPrice() |
No division by zero |
assertLeverageInBps |
computeLeverage() |
Leverage in sane range (0 to 10,000x) |
assertOracleExponentDynamic |
getOraclePriceRange() |
Exponent read from oracle, not hardcoded |
assertBorrowRateBound |
computeBorrowRate() |
Rate <= base + slope1 + slope2 |
Guards never fire under normal operation. They exist to catch:
- Corrupted on-chain account data
- Engine bugs introduced by refactoring
- Unexpected oracle configurations
assessPositionRisk() wraps the entire computation in a try/catch that re-throws with position context (address, side, size) for fast debugging.
When invoked with --audit, the engine:
- Fetches all on-chain state (same as normal mode)
- Computes SHA-256 of the IDL file used for deserialization
- Outputs every computed value in three formats:
raw | scaled | human - Selects the highest-leverage position and traces every computation step
- Lists all values that cannot be verified against production
- Exits without running the full engine output
This mode is designed for independent review. Every number can be cross-checked against on-chain data using a block explorer or direct RPC queries.
Normal mode executes five sequential phases:
- Source Verification -- Program ID, IDL version, RPC endpoint
- Parameter Extraction -- Fetch pools, custodies, markets, oracles. Display custody parameters.
- Risk Engine -- Fetch positions. Assess per-position risk. Compute summary, density map, leverage distribution.
- Utilization Metrics -- Per-custody utilization and borrow rate forecasting.
- Sanity Checks + Summary -- Automated invariant validation. Final risk snapshot with natural-language summary.
Each phase depends on data from previous phases. WebSocket subscription (optional) runs as a sixth phase that keeps the process alive for real-time monitoring.