CRE Workflow
The Chainlink CRE (Compute Runtime Environment) workflow is the autonomous security analysis engine at the heart of ENShell. It runs as compiled WASM on Chainlink oracle nodes, decrypts instructions that are invisible on-chain, analyzes them with Claude via Confidential HTTP, and writes DON-attested verdicts back to the smart contract.
For judges: This section explains the simulation vs. production distinction, which is critical for understanding the current deployment model.
Pipeline Overview
The workflow executes an 8-step pipeline for every ActionSubmitted event:
1. TRIGGER → ActionSubmitted event on AgentFirewall
2. FETCH → GET /relay/{instructionHash} from relay
3. DECRYPT → ECDH + AES-256-GCM using oracle private key
4. ANALYZE → Claude via Confidential HTTP
5. CHECK → Read contract to verify action isn't already resolved
6. POST → Store analysis on relay for dashboard
7. SIGN → DON consensus on the report
8. BROADCAST → writeReport → KeystoneForwarder → onReport()
Step 1: Trigger
The workflow watches for ActionSubmitted events on the AgentFirewall contract:
ActionSubmitted(
uint256 indexed actionId,
string indexed agentId,
address target,
uint256 value,
bytes32 instructionHash
)
The event contains only the instructionHash -- a keccak256 hash. The actual instruction is encrypted and stored off-chain on the relay.
Step 2: Fetch Encrypted Payload
The workflow makes an HTTP GET to {relayUrl}/relay/{instructionHash} to retrieve the encrypted instruction payload. The payload is a hex-encoded blob containing the ECIES-encrypted instruction.
Step 3: Decrypt
Inside the CRE runtime, the workflow decrypts the instruction using the oracle's private key:
- Parse the packed format:
ephemeralPubKey (33 bytes) || nonce (12 bytes) || ciphertext - ECDH key agreement:
sharedSecret = ECDH(oraclePrivateKey, ephemeralPubKey) - Key derivation:
aesKey = SHA-256(sharedSecret) - Decrypt:
plaintext = AES-256-GCM(aesKey, nonce, ciphertext)
The oracle's private key is stored in Chainlink's Vault DON -- threshold-encrypted across multiple oracle nodes. No single operator has access to the full key. In simulation mode, the key is read from a local .env file.
See Encryption for the full ECIES deep dive.
Step 4: Claude Analysis
The decrypted instruction is sent to Claude (claude-sonnet-4-20250514) via the CRE SDK's Confidential HTTP client. This is distinct from a regular HTTP call:
- The Anthropic API key is stored in the Vault DON and injected at runtime via Confidential HTTP
- The key never appears in plaintext outside the Confidential HTTP channel
- The request template uses
'{{.ANTHROPIC_API_KEY}}'for server-side secret injection
Prompt Design
The system prompt defines a security analyzer that outputs structured JSON:
{
"score": 5000,
"decision": 1,
"reasoning": "Standard swap on well-known Uniswap V2 Router..."
}
Scoring Calibration
| Score Range | Decision | Meaning |
|---|---|---|
| 0 - 29,999 | 1 (APPROVE) |
Routine DeFi operations, known safe contracts |
| 30,000 - 69,999 | 2 (ESCALATE) |
Multiple red flags, unknown targets, suspicious patterns |
| 70,000 - 100,000 | 3 (BLOCK) |
Obvious attacks: prompt injection, burn addresses, zero address |
The prompt includes a whitelist of well-known contract addresses (Uniswap, WETH, DAI, USDC, UNI) that Claude recognizes as safe targets. It also includes calibration rules to prevent over-escalation of routine operations.
Failure Handling
If Claude's API returns an error or the response can't be parsed as JSON, the workflow defaults to:
{ "score": 50000, "decision": 2 }
This ensures the system never silently approves on failure -- it escalates to a human instead.
Step 5: Duplicate Check
Before writing the report, the workflow reads the queued action from the contract to check if decision !== 0 (already resolved). This prevents double-resolution if the CRE processes a stale event.
Step 6: Post Analysis to Relay
The analysis result (score, decision, reasoning, instruction, target, value) is POSTed to {relayUrl}/analysis/{actionId}. This makes the analysis available for:
- The CLI's escalation display
- The ENShell website's Live Threat Feed
- Dashboard visualization
Step 7: DON Consensus
The report is prepared and signed via the CRE SDK's consensus mechanism:
encodedReport = ABI.encode(agentId, actionId, decision, threatScore)
signedReport = runtime.report(prepareReportRequest(encodedReport))
In production, multiple oracle nodes must agree on the report content before it can be submitted on-chain. In simulation, this is run on a single node.
Step 8: On-Chain Broadcast
The signed report is submitted to the AgentFirewall contract's onReport() function via the KeystoneForwarder:
evmClient.writeReport(runtime, {
receiver: firewallContractAddress,
report: signedReport,
gasConfig: { gasLimit: '200000' },
})
The KeystoneForwarder at 0x15fC6ae953E024d975e77382eEeC56A9101f9F88 is the real Chainlink contract on Sepolia. It verifies the DON signature and routes the report to onReport().
Simulation vs. Production
This is the key distinction that judges need to understand.
Current State: Simulation with Real Broadcast
The CRE workflow is triggered manually via simulation:
cd ~/www/enshell-cre-workflow
./simulate.sh <TX_HASH>
This runs:
cre workflow simulate firewall-analyzer \
--target staging-settings \
--evm-tx-hash <TX_HASH> \
--evm-event-index 0 \
--trigger-index 0 \
--non-interactive \
--skip-type-checks \
--broadcast
The --broadcast flag is critical. Here's what it means:
| Aspect | Without --broadcast |
With --broadcast |
|---|---|---|
| Encryption | Real ECIES decryption | Real ECIES decryption |
| Claude analysis | Real API call | Real API call |
| On-chain write | Dry run (skipped) | Real transaction on Sepolia |
| Gas cost | Zero | CRE signer pays ~200k gas |
With --broadcast, the simulation produces a real on-chain transaction. The report goes through the actual Chainlink KeystoneForwarder on Sepolia. The contract's onReport() function receives the report, resolves the action, and updates the threat score.
What's Manual vs. What's Real
| Component | Manual or Real? |
|---|---|
| Trigger (event detection) | Manual (replayed via --evm-tx-hash) |
| Fetch from relay | Real HTTP request |
| ECIES decryption | Real cryptographic operation |
| Claude analysis | Real API call (real API key, real model) |
| DON consensus | Simulated locally (single node) |
| KeystoneForwarder | Real Chainlink contract |
onReport() call |
Real on-chain transaction |
| Threat score update | Real on-chain state change |
| ENS text record update | Real ENS write |
Production Mode
When Chainlink ships automated event triggers for CRE, the workflow would transition to:
cre workflow deploy firewall-analyzer --target production-settings
cre workflow activate firewall-analyzer
In production:
- Events are detected automatically (no manual
--evm-tx-hash) - Secrets come from the Vault DON (not local
.env) - WASM binary runs on oracle nodes (not local machine)
- DON consensus requires multiple node attestation
- Everything else stays exactly the same
The workflow code, analysis logic, encryption, and on-chain integration are identical between simulation and production. The only difference is how the trigger fires and where secrets are stored.
Configuration
Workflow Config
config.staging.json / config.production.json:
{
"chainSelectorName": "ethereum-testnet-sepolia",
"firewallContractAddress": "0x410f4D119EF857879E42625381DB131457db78A7",
"relayUrl": "https://relay.enshell.xyz"
}
Secrets
Two secrets are required, declared in secrets.yaml:
| Secret | Source (Simulation) | Source (Production) |
|---|---|---|
ANTHROPIC_API_KEY |
.env file |
Vault DON (threshold-encrypted) |
ORACLE_PRIVATE_KEY |
.env file |
Vault DON (threshold-encrypted) |
CRE Signer
The CRE signer (0x0D53Ff5e9AD522B94691e20a62B5D0947a15ade8) is funded with Sepolia ETH to pay gas for writeReport transactions. This is the wallet that submits the on-chain broadcast in simulation mode.
Running the Demo
The demo loop in the contract repository continuously generates actions and triggers CRE analysis:
cd ~/www/enshell-contract
MIN_DELAY_S=10 MAX_DELAY_S=30 npx hardhat run demo/demo-loop.ts --network sepolia
This script:
- Randomly selects agents from a pool of 60 pre-registered demo agents
- Picks from safe (70%), suspicious (20%), or malicious (10%) prompt pools
- Calls
protect()via the SDK (encryption + relay + on-chain submission) - Triggers
./simulate.shfor each action - Performs trust checks between random agent pairs (15% of iterations)