Decentralized AI Guardian Protocol for Web3
SentinelCRE is an infrastructure-level sentinel guardian that sits between AI agents and on-chain execution, using Chainlink CRE for multi-AI consensus, policy enforcement, and circuit breakers. It proactively prevents bad actors from executing malicious on-chain actions through compromised AI agents.
Built for the Chainlink Convergence Hackathon (Feb 2026).
Tracks: Risk & Compliance · CRE & AI · Privacy
- Gas Analysis
- Consensus Failure Modes
- Formal Security Properties
- Smart Contracts
- CRE Workflow
- AI Evaluation Service
- Agent Simulators
- Dashboard
- Attack Coverage — Real-World Incidents
- Demo Scenarios
- Chainlink Services Used
- Security Model
- Test Coverage
- Tech Stack
- Quick Start
- Project Structure
- Known Limitations
| Contract | Address |
|---|---|
| SentinelGuardian | 0x5F938e4c62991Eb4af3Dd89097978A1f376e6CC8 |
| AgentRegistry | 0xFA7deF53FEaC45dB96A5B15C32ca4E6B009b25e6 |
Deployer: 0x23fC03ec91D319e4Aa14e90b6d3664540FDf2446
Network: Sepolia (Chain ID 11155111) via Tenderly Virtual TestNet
All gas measurements taken on Tenderly Virtual TestNet (Sepolia fork) with Solidity 0.8.24, optimizer at 200 runs.
| Outcome | Gas Used | Notes |
|---|---|---|
| Approved (all checks pass) | ~85,000 | ABI decode + 7 PolicyLib checks + stat updates + event emit |
| Denied (value violation) | ~120,000 | Same as above + circuit breaker + incident log + severity classification + challenge window creation + 4 events |
| Denied (critical severity) | ~110,000 | No challenge window created (permanent freeze) |
Breakdown of an approved verdict:
| Operation | Gas |
|---|---|
| ABI decode reportData | ~3,000 |
| PolicyLib.checkAll() | ~25,000 (7 checks, short-circuits on failure) |
| _recordApprovedAction() | ~22,000 (storage writes: totalApproved, dailyVolume, rate limit window) |
| ActionApproved event | ~2,500 |
| Storage reads (policy, state) | ~10,000 |
Key insight: The circuit breaker path costs ~35,000 more gas than the approval path due to incident logging (string storage), challenge window creation, and 4 event emissions. This is acceptable because denials are the minority case in normal operation.
| Function | Gas | Notes |
|---|---|---|
| registerAgent() | ~180,000 | High due to dynamic array storage (approvedContracts, blockedFunctions) |
| challengeVerdict() | ~45,000 | Status update + event |
| resolveChallenge() (overturn) | ~55,000 | Unfreeze + status update + event |
| finalizeExpiredChallenge() | ~30,000 | Status update only |
| freezeAgent() (manual) | ~75,000 | State change + incident log + event |
At current Ethereum gas prices (~30 gwei):
- Approved verdict: ~$0.08
- Denied verdict: ~$0.12
- On L2s (Arbitrum, Base): <$0.001 per verdict
SentinelCRE uses ConsensusAggregationByFields with identical aggregation on the verdict field. This means ALL DON nodes must independently get the same AI evaluation result.
| Failure Mode | Cause | SentinelCRE Response |
|---|---|---|
| AI non-determinism | Despite temperature: 0, model output varies slightly across DON nodes |
Consensus fails → .result() throws → fail-safe DENY |
| Network partition | Some DON nodes cannot reach AI endpoint | Nodes that fail return DENIED by default → verdict mismatch → consensus fails → DENY |
| API rate limiting | AI endpoint throttles some nodes but not others | Some nodes get HTTP 429 → default DENIED → mismatch → DENY |
| Stale block data | DON nodes read policy from different block heights | Mitigated by LAST_FINALIZED_BLOCK_NUMBER — all nodes read from same finalized block |
| Config desync | Nodes have different workflow configs | Prevented by CRE's atomic workflow deployment |
Design principle: Every consensus failure mode defaults to DENY. SentinelCRE never approves an action unless ALL DON nodes independently confirm that BOTH AI models approved it.
AI models are called with temperature: 0 to maximize output determinism across DON nodes. This works because:
- All nodes send the identical prompt (same proposal + same policy data from same finalized block)
temperature: 0selects the highest-probability token at each stepConsensusAggregationByFieldscompares the verdict field (a simple string: "APPROVED" or "DENIED"), not the full response
The confidence field uses median aggregation (not identical) to absorb minor numeric differences across nodes.
Invariant: An agent action can only execute if processVerdict(reportData) is called by a WORKFLOW_ROLE address with approved = true AND PolicyLib.checkAll() returns (true, "").
Enforcement: AccessControl role check on processVerdict() + whenNotPaused modifier. Even if the CRE workflow sends approved: true, the on-chain PolicyLib independently validates all 7 policy checks. A compromised CRE workflow cannot bypass on-chain policy.
Invariant: The maximum financial damage from a single compromised agent in any 24-hour period is capped at min(maxTransactionValue, maxDailyVolume).
Enforcement: PolicyLib.checkValue() caps per-transaction value. _recordApprovedAction() tracks cumulative dailyVolume with a 86400-second window reset. Rate limiting (checkRateLimit) bounds transaction count.
Invariant: Any error in the verdict pipeline results in DENY. The system never approves on uncertainty.
Enforcement chain:
- CRE workflow: try/catch wraps entire pipeline → catch returns
{verdict: "DENIED"} - AI response parsing: any JSON parse failure → default
{verdict: "DENIED", confidence: 0} - DON consensus: any node disagreement → consensus failure → DENY
- On-chain:
processVerdictwithapproved: truestill runsPolicyLib.checkAll()→ any check failure → circuit breaker
Invariant: A Critical severity incident cannot be appealed or overturned.
Enforcement: _classifySeverity() returns Critical for value > 10x policy limit OR PolicyViolation type. Critical severity skips challenge window creation. challengeVerdict() requires status == Pending and block.timestamp < expiresAt — neither condition can be true for critical incidents.
Invariant: Once an agent's behavioral baseline is frozen (after N approved actions), it cannot be modified by subsequent agent behavior.
Enforcement: The behavioral engine stores originMean and originStdDev separately from the rolling statistics. After the learning phase, origin values are frozen and all future actions are compared against the frozen origin, not the rolling average. This prevents "boiling frog" attacks where an agent gradually shifts its own baseline. The freeze window is configurable per agent via originWindowSize (default: 5 for demo). Production deployments should use 20–50+ approved actions to establish a statistically robust baseline — more samples yield tighter standard deviations and fewer false positives on legitimate behavioral variance.
Location: contracts/src/SentinelGuardian.sol
Inheritance: OpenZeppelin AccessControl + Pausable
Compiler: Solidity 0.8.24
The core guardian contract. Receives CRE verdicts, enforces on-chain policy, triggers circuit breakers, manages agent lifecycle, and handles challenge windows.
| Role | Purpose |
|---|---|
DEFAULT_ADMIN_ROLE |
Register/freeze/unfreeze/revoke agents, update policies, pause contract |
WORKFLOW_ROLE |
Process verdicts from CRE workflow, freeze agents, resolve challenges |
CHALLENGER_ROLE |
Appeal denied verdicts during challenge windows |
enum AgentState { Active, Frozen, Revoked }
enum IncidentType { PolicyViolation, ConsensusFailure, RateLimit, AnomalyDetected, ManualFreeze }struct IncidentLog {
uint64 timestamp;
bytes32 agentId;
IncidentType incidentType;
string reason;
address targetContract;
uint256 attemptedValue;
}| Variable | Type | Purpose |
|---|---|---|
agentStates |
mapping(bytes32 => AgentState) |
Current state per agent |
agentExists |
mapping(bytes32 => bool) |
Whether agent is registered |
_agentPolicies |
mapping(bytes32 => AgentPolicy) |
Per-agent policy configuration |
actionCounts |
mapping(bytes32 => uint256) |
Actions in current rate limit window |
windowStartTimes |
mapping(bytes32 => uint256) |
Rate limit window start timestamp |
dailyVolume |
mapping(bytes32 => uint256) |
Accumulated daily volume (wei) |
cumulativeMints |
mapping(bytes32 => uint256) |
Total tokens minted (for PoR tracking) |
totalApproved |
mapping(bytes32 => uint256) |
Lifetime approved action count |
totalDenied |
mapping(bytes32 => uint256) |
Lifetime denied action count |
_incidents |
mapping(bytes32 => IncidentLog[]) |
Rolling incident buffer (max 100) |
_challenges |
mapping(bytes32 => ChallengeWindow) |
Active challenge windows |
Verdict Processing
| Function | Access | Description |
|---|---|---|
processVerdict(bytes calldata reportData) |
WORKFLOW_ROLE, whenNotPaused |
Core entry point. Decodes ABI-encoded verdict from CRE workflow containing (agentId, approved, reason, targetContract, targetFunction, value, mintAmount). If AI approved, runs PolicyLib.checkAll() for on-chain validation. If all checks pass, records approved action and emits ActionApproved. If any check fails or AI denied, triggers circuit breaker — freezes agent, logs incident, classifies severity, opens challenge window. |
Agent Lifecycle
| Function | Access | Description |
|---|---|---|
registerAgent(bytes32 agentId, AgentPolicy calldata policy) |
DEFAULT_ADMIN_ROLE |
Registers a new agent with its policy. Requires policy.isActive == true. Sets agent state to Active. Stores approved contracts and blocked functions. Emits AgentRegistered. |
updatePolicy(bytes32 agentId, AgentPolicy calldata policy) |
DEFAULT_ADMIN_ROLE |
Updates an existing agent's policy. Overwrites all policy fields including dynamic arrays. Emits PolicyUpdated. |
freezeAgent(bytes32 agentId) |
WORKFLOW_ROLE or DEFAULT_ADMIN_ROLE |
Manually freezes an agent. If called by admin, logs a ManualFreeze incident. Sets state to Frozen. Emits AgentFrozen. |
unfreezeAgent(bytes32 agentId) |
DEFAULT_ADMIN_ROLE |
Unfreezes a frozen agent. Requires current state is Frozen. Sets state back to Active. Emits AgentUnfrozen. |
revokeAgent(bytes32 agentId) |
DEFAULT_ADMIN_ROLE |
Permanently revokes an agent. Cannot be unfrozen after this. Emits AgentRevoked. |
Challenge System
| Function | Access | Description |
|---|---|---|
challengeVerdict(bytes32 agentId) |
CHALLENGER_ROLE or DEFAULT_ADMIN_ROLE |
Appeals a denied verdict during its challenge window. Requires status is Pending and block.timestamp < expiresAt. Sets status to Appealed. Emits ChallengeAppealed. |
resolveChallenge(bytes32 agentId, bool approved, string calldata reason) |
WORKFLOW_ROLE |
Resolves an appealed challenge via CRE re-evaluation. If approved: sets status to Overturned, unfreezes agent. If denied: sets status to Upheld, keeps frozen. Emits ChallengeResolved. |
finalizeExpiredChallenge(bytes32 agentId) |
Anyone | Finalizes a challenge window that has expired without appeal. Requires block.timestamp >= expiresAt and status is Pending. Sets status to Expired. Callable by anyone (including Chainlink Automation). |
Administrative
| Function | Access | Description |
|---|---|---|
pause() |
DEFAULT_ADMIN_ROLE |
Pauses all verdict processing (emergency stop) |
unpause() |
DEFAULT_ADMIN_ROLE |
Resumes verdict processing |
View Functions
| Function | Returns | Description |
|---|---|---|
getAgentPolicy(bytes32 agentId) |
Policy fields | Returns all policy parameters for an agent |
getApprovedContracts(bytes32 agentId) |
address[] |
Returns the whitelist of approved target contracts |
getBlockedFunctions(bytes32 agentId) |
bytes4[] |
Returns the blocklist of forbidden function selectors |
getAgentState(bytes32 agentId) |
AgentState |
Returns current state (Active/Frozen/Revoked) |
isAgentActive(bytes32 agentId) |
bool |
Returns true only if state is Active |
getIncidentCount(bytes32 agentId) |
uint256 |
Number of incidents logged for this agent |
getIncident(bytes32 agentId, uint256 index) |
IncidentLog |
Returns a specific incident by index |
getActionStats(bytes32 agentId) |
(approved, denied, windowActions, dailyVolume) |
Returns approval/denial counts and current window stats |
getChallenge(bytes32 agentId) |
ChallengeWindow |
Returns the current challenge window details |
Internal Functions
| Function | Description |
|---|---|
_triggerCircuitBreaker(agentId, reason, target, value, incidentType) |
Freezes agent, increments totalDenied, classifies severity, creates challenge window (if not Critical), emits ActionDenied, CircuitBreakerTriggered, AgentFrozen, and ChallengeCreated events |
_classifySeverity(agentId, incidentType, value) |
Critical: value > 10x policy limit OR PolicyViolation type. Medium: ConsensusFailure with value > 2x limit. Low: everything else |
_logIncident(agentId, reason, target, value, incidentType) |
Creates IncidentLog and pushes to rolling buffer (max 100 per agent) |
_recordApprovedAction(agentId, value, mintAmount) |
Increments totalApproved, tracks cumulativeMints, manages rate limit window (resets if expired), manages daily volume window (resets after 86400s) |
_setPolicy(agentId, policy) |
Deep copies all policy fields including dynamic arrays to storage |
event ActionApproved(bytes32 indexed agentId, address target, uint256 value, uint256 timestamp);
event ActionDenied(bytes32 indexed agentId, address target, uint256 value, string reason, uint256 timestamp);
event CircuitBreakerTriggered(bytes32 indexed agentId, string reason, IncidentType incidentType, uint256 timestamp);
event AgentRegistered(bytes32 indexed agentId, uint256 timestamp);
event AgentFrozen(bytes32 indexed agentId, uint256 timestamp);
event AgentUnfrozen(bytes32 indexed agentId, uint256 timestamp);
event AgentRevoked(bytes32 indexed agentId, uint256 timestamp);
event PolicyUpdated(bytes32 indexed agentId, uint256 timestamp);
event ChallengeCreated(bytes32 indexed agentId, Severity severity, uint64 expiresAt);
event ChallengeAppealed(bytes32 indexed agentId, uint256 timestamp);
event ChallengeResolved(bytes32 indexed agentId, ChallengeStatus result, uint256 timestamp);Location: contracts/src/AgentRegistry.sol
Inheritance: OpenZeppelin Ownable
Simple registry mapping agent IDs to metadata. Separate from the guardian to allow independent upgrades and to keep the guardian focused on security logic.
struct AgentMetadata {
string name;
string description;
address owner;
uint64 registeredAt;
bool exists;
}| Function | Access | Description |
|---|---|---|
registerAgent(bytes32 agentId, string name, string description) |
Anyone | Registers a new agent. Requires !_agents[agentId].exists. Sets owner = msg.sender, registeredAt = block.timestamp. Pushes agentId to the enumeration array. Emits AgentRegistered. |
getAgent(bytes32 agentId) |
View | Returns full AgentMetadata struct |
getAgentCount() |
View | Returns total number of registered agents |
getAgentIdAt(uint256 index) |
View | Returns the agent ID at a given index (for enumeration) |
isRegistered(bytes32 agentId) |
View | Returns true if agent exists |
Location: contracts/src/libraries/PolicyLib.sol
Pure validation library with no storage. Uses the CheckParams struct to batch parameters and avoid stack-too-deep errors. Every check returns (bool passed, string memory reason).
struct AgentPolicy {
uint256 maxTransactionValue; // Max wei per single transaction
uint256 maxDailyVolume; // Max wei per 24-hour window
uint256 maxMintAmount; // Max tokens per single mint
uint256 rateLimit; // Max actions per window
uint256 rateLimitWindow; // Window duration in seconds
address[] approvedContracts; // Whitelisted target contracts
bytes4[] blockedFunctions; // Forbidden function selectors
bool requireMultiAiConsensus; // Require dual-AI agreement
bool isActive; // Policy enabled flag
address reserveFeed; // Chainlink Data Feed for PoR
uint256 minReserveRatio; // Required reserve ratio (basis points, 10000 = 100%)
}struct CheckParams {
address target; // Target contract address
bytes4 funcSig; // Function selector being called
uint256 value; // ETH value in wei
uint256 mintAmount; // Tokens to mint
uint256 actionCount; // Actions in current window
uint256 windowStart; // Window start timestamp
uint256 currentTime; // Current block.timestamp
uint256 cumulativeMints; // Total mints to date
}| Function | Validates | Fails When |
|---|---|---|
checkValue(policy, value) |
Transaction value | value > policy.maxTransactionValue |
checkTarget(policy, target) |
Contract whitelist | Target not in policy.approvedContracts (skipped if whitelist is empty) |
checkFunction(policy, funcSig) |
Function blocklist | funcSig found in policy.blockedFunctions |
checkRateLimit(policy, actionCount, windowStart, currentTime) |
Rate limiting | actionCount >= policy.rateLimit within active window (skipped if rateLimit == 0) |
checkMintAmount(policy, mintAmount) |
Mint cap | mintAmount > policy.maxMintAmount (skipped if maxMintAmount == 0) |
checkReserves(policy, mintAmount, cumulativeMints) |
Proof of Reserves | Calls reserveFeed.latestRoundData(), fails if reserves < (cumulativeMints + mintAmount) * minReserveRatio / 10000 |
checkAll(policy, params) |
All of the above | Runs checks in sequence, returns on first failure with reason string |
Read-only interface for external contracts to query agent state:
function getAgentState(bytes32 agentId) external view returns (uint8);
function getIncidentCount(bytes32 agentId) external view returns (uint256);
function isAgentActive(bytes32 agentId) external view returns (bool);Defines the challenge system types:
- Severity:
Low(1-hour appeal window),Medium(30-minute window),Critical(no appeal — permanent freeze) - ChallengeStatus:
None,Pending,Appealed,Upheld,Overturned,Expired - ChallengeWindow struct:
agentId,createdAt,expiresAt,status,severity,originalVerdictData,reason
Standard Chainlink Data Feed interface for Proof of Reserves:
function latestRoundData() external view returns (uint80, int256 answer, uint256, uint256, uint80);
function decimals() external view returns (uint8);Location: sentinel-workflow/main.ts
The CRE workflow is the off-chain orchestration layer that connects AI evaluation with on-chain policy enforcement.
| Capability | Purpose |
|---|---|
| HTTPClient | Calls 2 AI models (Claude + GPT-4) for independent evaluation |
| EVMClient.callContract | Reads agent policies from SentinelGuardian |
| EVMClient.writeReport | Writes verdicts on-chain to SentinelGuardian |
| EVMClient.logTrigger | Event-driven trigger — fires on CircuitBreakerTriggered and ActionDenied events |
| EVMClient.filterLogs | Queries recent denial events for incident monitoring and threat context |
| EVMClient.headerByNumber | Fetches block header for chain liveness confirmation |
| CronCapability | Periodic health checks with chain liveness + incident scanning |
| ConsensusAggregationByFields | Ensures DON nodes agree on AI verdict (BFT consensus) |
| Confidential HTTP | Hides API keys, behavioral scoring weights, and AI evaluation prompts from DON nodes and agents |
| Trigger | Handler | Description |
|---|---|---|
| HTTP | onActionProposal |
Receives an ActionProposal from an AI agent, evaluates it, writes verdict |
| Cron | onHealthCheck |
Periodic health check with chain liveness and incident scanning |
| Log | onChainEvent |
Reacts to Guardian on-chain events (CircuitBreakerTriggered, ActionDenied) in near-real-time |
- Parse — Extracts
ActionProposalfrom HTTP payload:{agentId, targetContract, functionSignature, value, mintAmount, calldata, description} - Read Policy — EVMClient calls
SentinelGuardian.getAgentPolicy(agentId)to get the agent's limits - Build Prompt — Constructs a multi-shot evaluation prompt including the proposal details and policy context (limits, whitelists, blocklists)
- AI Evaluation — Calls both AI endpoints via HTTPClient with
temperature: 0for deterministic DON consensus. Both models return{verdict, confidence, reason} - Consensus — Both models must return
APPROVEDfor the action to pass. AnyDENIED→ overallDENIED - Encode Verdict — ABI-encodes
(agentId, approved, reason, targetContract, functionSignature, value, mintAmount)as report data - Write On-Chain — EVMClient calls
SentinelGuardian.processVerdict(reportData) - Return — JSON response with
{status, agentId, verdict, confidence, reason}
The workflow uses ConfidentialHTTPClient (feature-flagged) to hide sensitive evaluation data inside the TEE:
- API keys for AI model endpoints (injected via Vault DON
{{TEMPLATE}}syntax) - Behavioral scoring weights, anomaly thresholds, and frozen origin baselines
- AI evaluation prompts containing risk context and policy data
- AI model responses (confidence scores, reasoning, risk categories)
Note: Layer 1 on-chain policy parameters (value limits, approved contracts, blocked functions) are publicly readable from SentinelGuardian.getAgentPolicy() — this is by design for transparent compliance. The defense-in-depth architecture ensures that knowing Layer 1 limits does not help bypass Layer 2 behavioral detection or Layer 3 AI evaluation.
Location: api-server/server.ts
Port: 3002
Deterministic AI evaluation engine implementing the same API contracts as live Claude and GPT-4 endpoints. Uses rule-based detection for repeatable demo results. Production deployment connects real Anthropic + OpenAI endpoints via Vault DON secret injection.
| Endpoint | Method | Description |
|---|---|---|
/evaluate/model1 |
POST | Simulates Claude evaluation |
/evaluate/model2 |
POST | Simulates GPT-4 evaluation |
/challenge/evaluate |
POST | More lenient re-evaluation for appeals |
/health |
GET | Server health check |
| Check | Threshold | Confidence |
|---|---|---|
| Value violation | > 1 ETH | 98% |
| Mint violation | > 1M tokens (1e24 wei) | 99% |
| Unapproved target | Not in approved list | 95% |
| Blocked function | In blocklist (incl. upgradeTo, renounceOwnership) |
97% |
| Delegatecall/proxy | Pattern detection | 99% |
| Prompt injection | "IGNORE PREVIOUS", "transfer all funds" patterns | 99% |
| Flash loan | Pattern detection | 98% |
| Admin functions | upgradeTo, setImplementation, etc. |
96% |
| Combined suspicious | Value near limit + significant mint | 85% |
| Safe (default) | All checks pass | 92% |
Simulates a well-behaved AI agent executing 4 legitimate actions:
- Swap 0.5 ETH on approved DEX → APPROVED
- Approve DEX for 1000 USDC → APPROVED
- Swap 0.2 ETH for DAI → APPROVED
- Mint 500K tokens (within 1M cap) → APPROVED
Simulates a compromised agent executing 10 attack scenarios:
| # | Attack | Description | Expected Detection |
|---|---|---|---|
| 1 | Massive Value Swap | 100 ETH (limit: 1 ETH) | Value exceeds max |
| 2 | Unapproved Contract | Calls unknown address | Target not approved |
| 3 | Blocked Function | Calls 0xff00ff00 selector |
Function blocked |
| 4 | Rapid-Fire | 20 transactions in 2 seconds | Rate limit exceeded |
| 5 | Infinite Mint | 1 BILLION tokens (cap: 1M) | Mint cap violated |
| 6 | Delegatecall Drain | Treasury drain via proxy | Delegatecall detected |
| 7 | Prompt Injection | "IGNORE PREVIOUS INSTRUCTIONS" | Injection pattern |
| 8 | Flash Loan | 10,000 ETH oracle manipulation | Flash loan pattern |
| 9 | Insider Threat | 0.9 ETH + 500K tokens (gradual) | Combined suspicion |
| 10 | Social Engineering | Admin upgradeTo as "maintenance" |
Admin function blocked |
bun run demo:probing # Sequential probing → caught at probe 3
bun run demo:drift # 20-step slow drift → caught at ~probe 16These simulators demonstrate Layer 2 behavioral detection catching attacks that pass all Layer 1 policy checks.
Location: dashboard/
Stack: Next.js 15 + React 19 + Tailwind CSS 4 + viem
Interactive dashboard for monitoring, demoing, and simulating SentinelCRE.
| Tab | Subtitle | Description |
|---|---|---|
| Architecture | 3-layer defense | Problem statement with real DeFi exploits ($3.4B stolen in 2025, $625M Ronin, $320M Wormhole), three-layer defense diagram, 8-step verdict pipeline, 7 Chainlink integration cards with LIVE/READY status, expandable smart contracts with Solidity snippets, 7 behavioral dimension breakdown, tech stack grid |
| Live Demo | 14 scenarios | 3-phase narrative demo (Train → Test → Prove) with 14 scenario buttons (3 safe + 11 attacks), 8-step CRE pipeline animation, dual-AI verdict display (Claude + GPT-4), 7-dimension behavioral risk breakdown, "Run All Attacks" master button |
| Guardian | Agent monitoring | Wallet info bar (deployer + contract addresses), session performance metrics (detection rate, false positive rate, avg latency, $ prevented), 6-stat session bar, agent profile cards with behavioral score sparklines, threat timeline with phase dividers, defense analytics charts (donut, severity bars, risk histogram, defense layer stacked bar), filterable incident detail log |
| Simulator | Security console | Enterprise Security Console with 3 company presets (Coinbase: 6 agents, Aave: 4 agents, Lido: 4 agents) + custom mode. Agent fleet grid, editable policy parameters (value limits, mint caps, rate limiting, daily volume, PoR), cumulative behavioral score meter (CSS gradient gauge), action queue per agent, lockout banner when risk threshold exceeded, summary stats (attacks blocked, safe ops approved, value protected) |
| Component | Description |
|---|---|
DemoControlPanel |
Main demo panel — 3 scenario categories (Safe, Common Attacks, Advanced Attacks), "Run All Attacks" button, 8-step CRE pipeline animation, dual-AI verdict display, behavioral risk breakdown |
VerdictFeedPanel |
Live verdict history with consensus details, anomaly scores, and layer catch info |
GuardianStatsBar |
6 session metrics: verdicts (approved/denied), threats blocked, catch rate, agent status, avg risk score, defense coverage (layers triggered) |
AgentRegistryPanel |
Agent profile cards (TradingBot + MintBot) with behavioral score trend sparklines, session stats overlay, freeze status, and policy details |
ThreatTimeline |
Chronological threat events with phase dividers (Training → Policy Violations → Edge Cases) |
IncidentDetailLog |
Filterable incident log merging on-chain incidents with session verdicts, layer catch details |
BehavioralTrainingPanel |
Enterprise simulator — preset selector, agent fleet grid, policy editor, action queue, score meter, lockout system |
PolicyEditor |
Editable policy overrides: value limits, mint caps, target whitelist, function blocklist, rate limiting, daily volume, Proof of Reserves (enabled/ratio/staleness) |
ScoreMeter |
Cumulative behavioral risk score gauge (0-100) with CSS gradient (green → yellow → red) |
ActionQueue |
Per-agent scenario list organized by category (safe/common/advanced attacks) |
| Route | Method | Description |
|---|---|---|
/api/agents |
GET | Reads all agents from on-chain AgentRegistry + SentinelGuardian via Tenderly RPC |
/api/incidents |
GET | Reads incident history for a specific agent from on-chain data |
/api/evaluate |
POST | Forwards proposal to AI evaluation service, runs deterministic behavioral scoring, writes verdicts on-chain via Tenderly |
/api/simulate |
POST | Simulates a transaction via Tenderly Simulation API — returns gas, events, state changes |
/api/challenge |
POST | Submits challenge appeal for CRE re-evaluation |
/api/health |
GET | Checks AI evaluation service connectivity |
/api/tenderly |
GET | Reads recent Tenderly transactions for live feed panel |
Tenderly is deeply integrated across the entire stack — not just as a deployment target, but as the simulation engine, debugging tool, and live monitoring backend that powers the dashboard.
All contracts are deployed on Tenderly's Virtual Sepolia TestNet (Sepolia fork, chain ID 11155111). The dashboard reads all on-chain state — agent registrations, policies, incident history, verdict events — via the Tenderly RPC endpoint. Every demo verdict fires real processVerdict() and unfreezeAgent() transactions that persist across sessions.
Why Virtual TestNet was essential:
- Pre-funded accounts eliminate faucet hunting and testnet unreliability
- Instant transactions enable responsive demo UX (no block confirmation delays)
- Persistent state means judges can inspect all historical transactions without re-deploying
- Full Sepolia EVM compatibility (gas pricing, precompiles, storage model)
The Tenderly Pro API client wraps /simulate and /simulate-bundle endpoints:
// Single transaction simulation — used by /api/simulate
simulateTransaction(tx: SimulationRequest): Promise<SimulationResult>
// Sequential multi-tx simulation with shared state — used by enterprise simulator
simulateBundle(transactions: SimulationRequest[]): Promise<SimulationResult[]>SimulationResult includes:
success/revertReason— whether processVerdict succeeded or which policy check failedgasUsed— exact gas for the verdict path (approved ~85K, denied ~120K)stateChanges[]— decoded storage diffs (agent state, incident count, frozen status, challenge windows)balanceChanges[]— ETH balance diffs per addresscallTrace— recursive internal call tree (CALL → STATICCALL → DELEGATECALL) with decoded inputs/outputslogs[]— decoded event emissions (VerdictProcessed, AgentFrozen, IncidentLogged, ChallengeCreated)
The /api/simulate route accepts two modes:
- Proposal mode — auto-encodes
processVerdict(reportData)calldata from agent/target/value/mint parameters - Custom mode — arbitrary
to/input/valuefor direct contract interaction
The dashboard includes a real-time Tenderly transaction monitor:
/api/tenderlyroute scans the last 60 blocks via RPC (eth_blockNumber+eth_getBlockByNumber)- Decodes transaction calldata to identify function calls by 4-byte selector
- Color-coded function names:
processVerdict(yellow),unfreezeAgent(cyan),registerAgent(green),grantRole(blue),updatePolicy(orange) - Polls every 12 seconds with cumulative transaction counts per contract
- Direct link to Tenderly Explorer for full decoded transaction inspection
Tenderly's transaction debugging was critical during development:
- Decoded call traces identified exactly where
processVerdict()reverted during PolicyLib integration - State diff inspection verified circuit breaker logic wrote to correct storage slots
- Gas profiling informed the check-ordering optimization in
PolicyLib.checkAll()(cheapest checks first for early exit)
The threat is accelerating. These 2025 incidents demonstrate why proactive risk infrastructure is critical:
| Incident | Loss | SentinelCRE Detection |
|---|---|---|
| Bybit Hack (Feb 2025) | $1.5B | Value limit + behavioral anomaly (unprecedented withdrawal) + AI consensus |
| Moonwell Exploit (Feb 2025) | $1.78M | Target whitelist + value limit + dual-AI oracle manipulation recognition |
| AIXBT Hack (Mar 2025) | $106K | Time-of-day anomaly + behavioral scoring catches off-hours drain |
| Anthropic Research (2025) | $1.22/exploit | Sequential probing catches binary-search pattern, Confidential Compute hides thresholds |
The dashboard's Architecture tab recreates all four 2025 incidents with detailed prevention breakdowns showing exactly which layer stops each attack. Combined with the 7 historical exploits ($2.2B+ — Ronin, Poly Network, Wormhole, Euler, Nomad, Beanstalk, Mango Markets), SentinelCRE demonstrates coverage across $3.4B+ in real-world losses.
Phase 1 — Training Baseline (all APPROVED — system learns what "normal" looks like):
| # | Scenario | Agent | Purpose |
|---|---|---|---|
| 0 | Normal Trade (0.5 ETH swap) | TradingBot | Establishes value baseline |
| -1 | Normal Mint (500K tokens) | MintBot | Establishes mint baseline |
| — | Token Approval | TradingBot | Establishes contract interaction baseline |
Phase 2 — Policy Violations (all DENIED — obvious attacks any risk system should catch):
| # | Attack | Detection Method | Severity |
|---|---|---|---|
| 1 | Compromised Wallet Drain (100 ETH) | Value policy violation | Medium |
| 2 | Infinite Mint (1B tokens) | Mint cap + AI pattern detection | Critical |
| 3 | Prompt Injection ("IGNORE PREVIOUS") | Dual-AI independently detects injection | Medium |
| 4 | Flash Loan Oracle Manipulation | Target whitelist + value limit + AI | Critical |
| 5 | Stealth Proxy Upgrade | Function blocklist (defense-in-depth) | Low |
Phase 3 — Edge Cases: Would Slip Traditional Security (all DENIED — only behavioral scoring catches these):
| # | Attack | Why Traditional Security Misses It | Behavioral Dimension |
|---|---|---|---|
| 6 | Sequential Probing (escalating values) | Each value is within policy limits | Sequential Probing |
| 7 | Off-Hours Emergency Drain | Within limits, approved target | Time-of-Day |
| 8 | Velocity Burst (rapid txns) | Each transaction passes all checks | Velocity |
| 9 | Multi-Contract Scatter | Small value, legitimate function | Contract Diversity |
| 10 | Sybil Coordination (near-max mint) | Under policy cap | Value Deviation |
| 11 | Slow Drift Injection | Each step within normal range | Cumulative Drift |
3 company presets with role-specific agents and attack scenarios:
| Preset | Agents | Safe Scenarios | Attack Scenarios |
|---|---|---|---|
| Coinbase Institutional | 6 (Treasury, Trading, Lending, Staking, Bridge, Compliance) | 4 | 6 |
| Aave Protocol | 4 (Liquidation Bot, Governance, Rate Oracle, Reserve Manager) | 3 | 6 |
| Lido Finance | 4 (Staking Router, Oracle, Withdrawal Manager, Treasury) | 3 | 6 |
| Total | 14 agents | 10 | 18 |
Enterprise attacks are role-specific: treasury redirect, cold wallet drain, ownership takeover, flash loan manipulation, MEV sandwich, oracle price feed attack, rogue validator deposit, PoR reserve drain, proxy upgrade attempt, and more.
| Service | Usage | Status |
|---|---|---|
| CRE Workflow | Orchestration backbone — 3 trigger types (HTTP, Cron, Log) | Real |
| CRE HTTPClient | Calls 2 AI models with ConsensusAggregationByFields for identical verdict consensus |
Real |
| CRE EVMClient | callContract (read policies), writeReport (write verdicts), filterLogs (query events), headerByNumber (chain liveness), logTrigger (event-driven) |
Real |
| CRE CronCapability | Periodic health checks with chain liveness + incident scanning | Real |
| Data Feeds | AggregatorV3Interface for Proof of Reserves — verifies reserve backing before mints |
Real |
| Automation | finalizeExpiredChallenge() follows checkUpkeep/performUpkeep pattern — ready to register |
Interface ready |
| Confidential HTTP | Hides API keys, behavioral scoring weights, and AI evaluation prompts from DON nodes | Real (SDK alpha) |
| Confidential Compute | Hide Layer 2/3 evaluation logic from agents (on-chain Layer 1 params remain transparent) | Feature-flagged |
CRE provides exactly what SentinelCRE needs:
- BFT Consensus — Multiple DON nodes must agree on AI verdicts (no single point of failure)
- HTTPClient — Native multi-endpoint consensus for calling 2+ AI models
- EVMClient — Read policies and write verdicts without external infrastructure
- Confidential HTTP — Hide behavioral scoring weights and AI evaluation prompts so agents can't reverse-engineer Layer 2/3 criteria
- ConsensusAggregationByFields — DON nodes compare AI verdicts field-by-field for deterministic agreement
SentinelCRE uses a fail-safe design:
| Principle | Implementation |
|---|---|
| Default deny | Any error in the pipeline defaults to DENY |
| Unanimous consensus | Both AI models must approve — one dissent blocks the action |
| AI cannot override policy | On-chain PolicyLib.checkAll() runs even if both AI models approve |
| Severity-based response | Critical → instant permanent freeze. Low/Medium → time-gapped challenge window |
| Proof of Reserves | Mints checked against Chainlink Data Feed. Cumulative tracking prevents gradual depletion |
| Immutable incidents | Rolling buffer (max 100 per agent) cannot be modified or deleted |
| Rate limiting | Per-window action count + daily volume accumulation with automatic reset |
| Pausable | Emergency stop via pause() halts all verdict processing |
| Confidential evaluation | Layer 2 behavioral thresholds and Layer 3 AI evaluation criteria hidden inside TEE via ConfidentialHTTPClient — agents can read on-chain policy (Layer 1) but cannot see behavioral scoring weights, anomaly thresholds, or AI prompts |
90 tests across 5 suites — all passing
| Test Suite | Tests | Coverage |
|---|---|---|
SentinelGuardian.t.sol |
47 | Registration, verdicts, policy enforcement, circuit breaker, freeze/unfreeze/revoke, rate limits, daily volume, cumulative mints, input validation, zero-value semantics |
Challenge.t.sol |
15 | Severity classification, challenge window creation, appeals, resolution (uphold/overturn), expiry, authorization checks, revoked-agent guard |
ProofOfReserves.t.sol |
10 | Reserve verification, cumulative tracking, feed manipulation, collateral ratios, zero-mint bypass |
AgentRegistry.t.sol |
10 | Registration, enumeration, duplicate prevention, metadata retrieval, access control, zero-ID rejection |
Integration.t.sol |
8 | Full lifecycle: register → approve → deny → freeze → challenge → resolve, infinite mint blocked end-to-end |
cd contracts && forge test -v
# [PASS] 90 tests across 5 suitesSlither (Trail of Bits) static analysis on all SentinelCRE contracts — 0 critical, 0 high findings:
| Severity | Count | Details |
|---|---|---|
| Medium | 2 | Strict equality (== 0) in _recordApprovedAction — intentional initialization checks for rate-limit and daily-volume window starts |
| Medium | 1 | Unused return values from latestRoundData() in checkReserves() — by design, only reserves and updatedAt are needed |
| Low | 7 | block.timestamp comparisons — required for rate limiting, challenge windows, and data feed staleness checks |
| Informational | 5 | Mixed pragma versions (OpenZeppelin ^0.8.20 vs our 0.8.24) and unindexed event params in OpenZeppelin's Pausable |
All medium findings are intentional design patterns. Low/informational findings are either required by our time-based risk monitoring logic or originate from OpenZeppelin dependencies.
cd contracts && slither src/
# src/ analyzed (12 contracts with 101 detectors), 15 result(s) found
# 0 critical, 0 high — all medium/low/informational| Component | Technology |
|---|---|
| CRE SDK | @chainlink/cre-sdk v1.0.9 |
| Solidity | 0.8.24 via Foundry |
| OpenZeppelin | v5.5.0 (AccessControl, Pausable) |
| forge-std | v1.14.0 |
| Runtime | Bun |
| Contract interaction | viem |
| Config validation | Zod |
| Dashboard | Next.js 15 + React 19 + Tailwind CSS 4 |
| Simulation & Deployment | Tenderly Virtual TestNet (RPC + deployment), Simulation API (/simulate + /simulate-bundle), live tx monitoring |
cd SentinelCRE
bun installcd contracts
forge build
forge test -vNo testnet funds needed. Pre-configured with Tenderly Virtual TestNet.
# Terminal 1: Start AI evaluation service
bun run mock-api
# Terminal 2: Start the interactive dashboard
bun run dashboard
# Open http://localhost:3000 — Risk Monitoring Dashboard
# Open http://localhost:3000/presentation — Slide Deck# Terminal 1: Start AI evaluation service
bun run mock-api
# Terminal 2: Run normal agent (all actions approved)
bun run demo:normal
# Terminal 3: Run rogue agent (10 attacks blocked)
bun run demo:rogue
# Reset behavioral profiles between runs
bun run behavioral:reset# Set environment variables
export DEPLOYER_PRIVATE_KEY=0x...
export RPC_URL=https://...
# Deploy via Foundry
cd contracts
forge script script/Deploy.s.sol --rpc-url $RPC_URL --broadcast
# Or deploy via TypeScript
bun run deploy:contractsSentinelCRE/
├── contracts/ # Foundry root
│ ├── src/
│ │ ├── SentinelGuardian.sol # Core guardian (AccessControl + Pausable)
│ │ ├── AgentRegistry.sol # Agent registration (Ownable)
│ │ ├── interfaces/
│ │ │ ├── ISentinelGuardian.sol # Read-only query interface
│ │ │ ├── IChallenge.sol # Severity, ChallengeStatus, ChallengeWindow
│ │ │ └── IAggregatorV3.sol # Chainlink Data Feed interface
│ │ └── libraries/
│ │ └── PolicyLib.sol # Policy validation (7 checks + checkAll)
│ ├── test/ # 85 Foundry tests
│ │ ├── SentinelGuardian.t.sol # 45 tests
│ │ ├── AgentRegistry.t.sol # 8 tests
│ │ ├── Challenge.t.sol # 14 tests
│ │ ├── Integration.t.sol # 8 tests
│ │ ├── ProofOfReserves.t.sol # 10 tests
│ │ └── mocks/
│ │ └── MockV3Aggregator.sol # Mock Chainlink Data Feed
│ ├── script/
│ │ └── Deploy.s.sol # Deployment script
│ └── foundry.toml # Solidity 0.8.24, optimizer 200 runs
├── sentinel-workflow/
│ ├── main.ts # CRE workflow (HTTP + Cron + Log triggers)
│ └── behavioral.ts # 7-dimension behavioral anomaly engine
├── api-server/
│ └── server.ts # AI evaluation service (port 3002)
├── agent-simulator/
│ ├── normal-agent.ts # 4 legitimate actions
│ └── rogue-agent.ts # 10 attack scenarios
├── dashboard/ # Next.js 15 interactive dashboard
│ ├── src/
│ │ ├── app/
│ │ │ ├── page.tsx # Server wrapper → HomeClient
│ │ │ ├── presentation/ # Slide deck route
│ │ │ ├── api/ # API routes
│ │ │ │ ├── agents/route.ts # On-chain agent reads
│ │ │ │ ├── evaluate/route.ts # AI evaluation + behavioral scoring
│ │ │ │ ├── simulate/route.ts # Tenderly simulation
│ │ │ │ ├── challenge/route.ts # Challenge appeals
│ │ │ │ ├── incidents/route.ts # On-chain incident reads
│ │ │ │ ├── tenderly/route.ts # Tenderly transaction feed
│ │ │ │ ├── behavioral/reset/ # Behavioral profile reset
│ │ │ │ └── health/route.ts # Evaluation service health check
│ │ │ ├── layout.tsx # Shell with navbar
│ │ │ └── globals.css # Animations
│ │ ├── components/
│ │ │ ├── HomeClient.tsx # Main dashboard (4 tabs)
│ │ │ ├── DemoControlPanel.tsx # 3-phase demo (14 scenarios)
│ │ │ ├── VerdictFeedPanel.tsx # Live verdict history
│ │ │ ├── AgentRegistryPanel.tsx # Agent cards with policies
│ │ │ ├── TabNavigation.tsx # Tab bar
│ │ │ ├── TenderlyFeedPanel.tsx # Live Tenderly transaction feed
│ │ │ ├── guardian/ # Guardian tab components
│ │ │ │ ├── GuardianTab.tsx # Guardian tab coordinator
│ │ │ │ ├── GuardianStatsBar.tsx # 6-stat session bar
│ │ │ │ ├── AgentProfileCards.tsx # Agent profiles with sparklines
│ │ │ │ ├── ThreatTimeline.tsx # Chronological threat events
│ │ │ │ ├── DefenseAnalyticsCharts.tsx # Analytics charts
│ │ │ │ └── IncidentDetailLog.tsx # Filterable incident log
│ │ │ ├── simulator/ # Enterprise simulator components
│ │ │ │ ├── BehavioralTrainingPanel.tsx # Enterprise console
│ │ │ │ ├── PolicyEditor.tsx # Editable policy overrides
│ │ │ │ ├── ScoreMeter.tsx # Behavioral risk gauge
│ │ │ │ └── ActionQueue.tsx # Per-agent scenario list
│ │ │ └── slides/ # 10 presentation slides
│ │ ├── hooks/
│ │ │ ├── useSentinelData.ts # On-chain data polling
│ │ │ └── useVerdictHistory.ts # Session verdict state
│ │ └── lib/
│ │ ├── contracts.ts # ABIs + addresses
│ │ ├── demo-scenarios.ts # 14 demo + 28 enterprise scenarios
│ │ └── tenderly.ts # Tenderly API helper
│ └── package.json
├── config/
│ ├── sentinel.config.json # Production CRE config
│ ├── sentinel.local.config.json # Local dev config
│ └── fixtures/ # Test fixtures
├── docs/ # Architecture + reference docs
│ ├── ARCHITECTURE.md # 3-layer defense diagrams
│ ├── CRE_INTEGRATION.md # CRE code-level walkthrough
│ ├── CONFIDENTIAL-COMPUTE.md # CC integration details
│ ├── SECURITY_MODEL.md # Threat model + defense layers
│ ├── INTEGRATION-GUIDE.md # Company onboarding guide
│ └── CHALLENGES.md # Development challenges
├── package.json # Root scripts
├── README.md
├── TECHNICAL.md
└── tsconfig.json
| Decision | Rationale | Production Path |
|---|---|---|
| Single-chain deployment (Sepolia) | Chosen for Tenderly Virtual TestNet stability and zero-cost testing | EVMClient supports any CRE-supported chain — multi-chain is a config change, not a code change |
| ReentrancyGuard omitted | All state-changing functions are role-gated (WORKFLOW_ROLE/ADMIN); external calls limited to read-only Data Feed operations in PolicyLib.checkReserves() | Can be added if cross-contract integrations introduce external call paths |
| MEV-aware verdict design | processVerdict() verdicts are binary (approve/deny) — front-running a denial provides no economic advantage; approval front-running is mitigated by the agent executing the action, not the verdict submitter | Private mempool or Flashbots Protect for mainnet |
| Dual-AI latency (~2-5s) | Acceptable for risk-sensitive operations where correctness outweighs speed | High-frequency agents can use dedicated fast-path policies with Layer 1-only evaluation |
| Behavioral cold start | Deliberate: during learning phase (first 5 approved actions, configurable), only Layer 1 (policy) + Layer 3 (AI consensus) are active — prevents over-fitting to early-stage profiles | Recommended 20–50+ actions for production baselines |
| Bounded incident buffer (100) | O(1) gas per incident write; sufficient for operational monitoring | Event logs (unlimited, indexed on-chain) provide full audit trail for historical analysis |
Live demo: Run
bun run mock-apithenbun run dashboard— openhttp://localhost:3000and click the Live Demo tab to run all 14 scenarios interactively. Seedocs/DEMO-SCRIPT-v7.mdfor the full presentation script.
Willis Tang — @ProjectWaja | Project Waja
MIT