SentinelFi
Autonomous covenant monitor with CRE consensus, smart metric simulation, and on-chain enforcement.
What it is
Private credit is a $1.7 trillion market where lenders set financial covenants - rules like "leverage must stay below 6.0x" and "DSCR must stay above 1.25x." Today, monitoring these covenants is entirely manual: every quarter, a human analyst reads a PDF financial report, extracts the numbers, checks them against thresholds, and if something is wrong, makes phone calls to trigger a freeze. This process is slow (days of delay), error-prone (single analyst, no verification), and reactive (borrower can default before anyone acts).
SentinelFi eliminates all three failure modes. It is an autonomous pipeline that:
- Triggers quarterly via a CRE CronTrigger - no human initiates anything.
- Fetches financial reports from an external data source via HTTPClient - each DON node independently.
- Extracts metrics via smart simulation proxy - every node calls a Gemini-format API endpoint (our Vercel-hosted proxy) that fetches the financial report server-side and extracts DSCR and leverage via regex. The workflow code is production-ready for real Gemini AI - only the geminiApiUrl config value needs to change.
- Achieves BFT consensus - consensusMedianAggregation computes the median across all DON nodes. Even if some nodes are compromised or return wrong values, the median is correct. This transforms independently-extracted data into BFT-verified data.
- Enforces on-chain - the consensus values are ABI-encoded and written to LoanFacility.sol via the Keystone Forwarder. The contract checks covenant thresholds and automatically freezes non-compliant loans in the same transaction - zero human delay.
The system manages a portfolio of multiple loans, each with independent covenant thresholds. A leverage of 5.9x might be healthy for Loan A (max 6.0x) but a breach for Loan B (max 5.0x). The contract handles this correctly per loan.
The dashboard reads directly from the blockchain - every value displayed (leverage, DSCR, frozen status, event history) comes from actual on-chain getAllLoans() calls and queryFilter() event queries. Nothing in the UI is hardcoded or faked.
How it Works
Smart Contract Layer - contracts/src/LoanFacility.sol
- Solidity 0.8.24 with OpenZeppelin AccessControl (role-based admin) + Pausable (emergency stop)
- Receives ABI-encoded (bytes32 loanId, uint256 leverage, uint256 dscr) via onReport() - callable only by the Keystone Forwarder
- Stores per-loan covenant thresholds AND last reported values on-chain (lastLeverage, lastDscr)
- Auto-freezes on breach, auto-unfreezes on recovery - emits CovenantBreached, CovenantHealthy, LoanFrozen, LoanUnfrozen events
- Portfolio view functions: getLoanIds(), getAllLoans(), getLoanHealth()
- 29 Foundry tests - all passing. Covers: healthy, leverage breach, DSCR breach, both breach, borderline, recovery, access control, pause/unpause, duplicate registration, stored values, multi-loan isolation
- Built and tested with Foundry v1.6.0, optimizer enabled (200 runs)
CRE Workflow - cre-workflow/src/workflow.ts - TypeScript using @chainlink/cre-sdk, viem, and zod
- Strictly follows all CRE hard rules: no async/await (.result() only), no Node builtins, no fetch (HTTPClient only), no Buffer (pure-JS base64 encoder), JSON key sorting for deterministic consensus
- Node Mode: Each DON node independently constructs a Gemini-format API request with sorted JSON keys containing REPORT_URL: in the prompt, calls the smart simulation proxy (which fetches the report server-side and extracts both DSCR and leverage via regex from the document text). In production this would be a real Gemini endpoint; for the demo it's our Vercel-hosted regex proxy.
- Single consensusMedianAggregation round per loan - both metrics packed into one number via combined encoding (dscr_scaled * 1000000 + leverage_scaled), keeping total HTTP calls to 3 (within CRE's 5-call limit)
- DON Mode: Scales float values to integers via Math.round() + BigInt(), ABI-encodes with encodeAbiParameters() from viem, writes to chain via EVMClient.writeReport()
- Config validated at runtime with Zod schema
Dashboard - frontend/index.html - Single-file Bloomberg-terminal-style UI (dark theme, IBM Plex Mono, scanline overlay)
- Uses ethers.js v6 with a shared provider singleton for direct on-chain reads
- Multi-loan portfolio table - reads all 3 loans via getAllLoans(), shows live leverage, DSCR, covenant thresholds, frozen status, last update timestamp
- Real-time metric cards with threshold bars - values come from on-chain state
- Simulation control panel - sends REAL Ethereum transactions to the contract (not mock UI), shows tx hash and block number
- CRE pipeline visualization - animated 6-stage diagram showing the actual workflow stages
- Live event log - queries on-chain events (CovenantBreached, LoanFrozen, etc.) via queryFilter()
- Polls every 5 seconds
Simulation Backend - mock-api/ - server.js - Express.js server with ethers.js v6
- POST /api/simulate - accepts {loanId, leverage, dscr}, ABI-encodes the payload, and calls onReport() on the deployed contract via the Forwarder key. Returns real tx hash and block number.
- GET /api/auto-demo - SSE endpoint that runs a scripted 4-step sequence (healthy -> borderline -> breach -> recovery) with real transactions and pipeline animation events
- GET /api/report - serves mock financial report text (the data source the CRE workflow fetches)
- api/gemini-proxy.ts - Vercel serverless function that simulates the Gemini API. It speaks the exact Gemini request/response format but extracts metrics via regex instead of LLM inference. Supports combined mode (fetches report server-side, returns both DSCR + leverage) and single mode (legacy fallback). The workflow code is identical whether pointing at this proxy or the real Gemini endpoint.
Links
Created by
- Arshdeep Singh
- Parth Singh