Pharos Sentinel

Pharos Sentinel

Evaluates on-chain risk for Pharos agents before executing transactions, providing verdicts (safe/caution/dangerous) and risk-bounded execution plans via Foundry cast reads.

Category
Visit Server

README

Sentinel โ€” a pre-action on-chain risk gate for Pharos agents

๐Ÿ”— Live demo: pharos-sentinel-production.up.railway.app โ€” run the risk gate against the live Atlantic fixtures (each /check executes real Foundry cast reads on Pharos Atlantic testnet).

This repository ships the Pharos Skill Engine with Sentinel added as its Step 0 โ€” risk pre-check. The engine (PharosNetwork/pharos-skill-engine) gives an AI agent the full Pharos on-chain toolkit โ€” balance/transaction queries, transfers, contract deploy & verify, and batch airdrops โ€” driven through Foundry (cast / forge). Sentinel is the reusable Skill this repo adds on top: an agent calls it before it moves value and gets a risk verdict (safe / caution / dangerous), the reasons behind it, and a risk-bounded execution plan. It is read-only โ€” it advises and blocks; it never signs or sends a transaction.

A pre-action risk check is the most-called primitive in any on-chain agent stack: every transfer, swap, or approval is a place an agent can lose funds. Sentinel makes that check a single, composable call, and wires it in as the engine's first write-operation pre-check.

What's in this package

This is the Pharos Skill Engine layout, with Sentinel slotted in as a skill:

  • SKILL.md โ€” the engine's agent entry point. Sentinel is registered in the Capability Index and as Step 0 of the Write-Operation Pre-checks.
  • references/ โ€” the engine's command references (query.md, transaction.md, contract.md, script-gen.md) plus sentinel.md, the risk-gate reference.
  • assets/ โ€” the engine's networks.json (Atlantic testnet + mainnet), tokens.json, ERC-20 / airdrop Solidity templates, and script-generation templates.
  • Sentinel runtime โ€” sentinel_skill.py (MCP server), sentinel_cli.py (CLI), pharos_atlantic.py (RPC reads), plus the demos, live risk gallery, x402 gate, and tests.

Sentinel runs on Atlantic testnet โ€” matching the engine's default network and its Piggy Bank reference skill โ€” while mainnet stays available in networks.json for the engine's other capabilities.

What makes it different

  • Read-only by design. Sentinel never holds keys and never sends a transaction โ€” the safest possible posture for a Skill that agents trust before moving money.
  • Foundry-native execution. Every on-chain read runs through the Foundry cast CLI โ€” the same toolchain the rest of the Skill Engine uses โ€” so Sentinel composes into the engine rather than bolting a separate client onto it. No indexer, no third-party API, no keys, no database.
  • Not just a verdict โ€” a plan. execution_plan returns approve/block plus bounded sizing within the caller's risk tolerance, so the agent gets a decision, not just a score.
  • Real EVM depth. Bytecode opcode analysis and proxy/ownership introspection, not surface heuristics (details below).

How it works

Sentinel sits between an agent and the chain. The agent declares an intent; Sentinel reads Pharos Atlantic by executing read-only Foundry cast commands (no keys, no transaction), scores what it finds, and returns a verdict, the reasons, and a risk-gated plan. The agent acts on the plan.

flowchart LR
    A["Pharos agent<br/>transfer ยท swap ยท approve ยท call"] -->|"address + action + amount"| S{{"Sentinel Skill<br/>risk_check / execution_plan"}}
    S -->|"cast reads ยท no keys ยท no tx"| C[("Pharos Atlantic<br/>chainId 688689")]
    C -->|"cast code ยท call ยท storage"| S
    S -->|"verdict + reasons + plan"| D{verdict}
    D -->|safe| P["Proceed"]
    D -->|caution| R["Reduce size / confirm"]
    D -->|dangerous| B["Block"]

A blocked approve under a strict safe-only tolerance, end to end (mirrors demo_agent.py):

sequenceDiagram
    autonumber
    participant Ag as Pharos agent
    participant Se as Sentinel
    participant Ch as Pharos Atlantic
    Ag->>Se: risk_check(addr, "approve", 100)
    Se->>Ch: cast code ยท cast call ยท cast storage
    Ch-->>Se: bytecode ยท owner ยท impl slot ยท paused
    Se-->>Ag: verdict "caution" + reasons
    Ag->>Se: execution_plan(addr, "approve", 100, max_risk "safe")
    Se-->>Ag: approved=false ยท BLOCK
    Note over Ag: agent halts โ€” no allowance is signed

Two tools

Tool Purpose
risk_check(address, action, amount_phrs?) Returns {verdict, score, reasons[], data{}}.
execution_plan(address, action, amount_phrs, max_risk) Risk-gated: approve/block + bounded sizing within tolerance.

action is one of transfer | swap | approve | call.

Risk signals (v2 โ€” read via Foundry cast)

  • Contract vs EOA, and action/target mismatches (e.g. approve to a non-token, or to a wallet).
  • Proxy detection: EIP-1167 minimal proxies and EIP-1967/1822 upgradeable proxies (the owner can swap the logic after you interact โ€” a real rug vector).
  • Bytecode opcode analysis: a proper opcode walk (stepping over PUSH immediates) that flags SELFDESTRUCT and DELEGATECALL used outside a known proxy pattern.
  • Ownership & upgrade-admin concentration: owner() + the EIP-1967 admin slot, distinguishing an EOA owner (higher centralization risk) from a contract owner (likely multisig/timelock).
  • ERC-20 introspection: symbol / decimals / totalSupply, with a zero-supply-trap flag.
  • Pausable state (paused()), tiny-bytecode stubs, and brand-new / zero-history counterparties (a typo & address-poisoning guard).

Every check starts at a perfect 100 and each signal subtracts from it, so risks stack into a single safety score (100 = safest, 0 = riskiest). The verdict is a band over it (>=66 safe, 31โ€“65 caution, <=30 dangerous). The band turns the score into the verdict, and execution_plan turns that into a decision:

flowchart TD
    addr["address + action"] --> isC{"has bytecode?"}
    isC -->|"no โ€” EOA"| eoa["EOA signals<br/>fresh / no history ยท action/target mismatch"]
    isC -->|"yes โ€” contract"| con["Contract signals"]
    con --> px["proxy<br/>EIP-1167 ยท 1967 ยท 1822"]
    con --> bc["bytecode<br/>SELFDESTRUCT ยท DELEGATECALL"]
    con --> own["ownership / upgrade-admin<br/>EOA vs contract"]
    con --> tok["ERC-20<br/>supply ยท zero-supply trap"]
    con --> st["pausable ยท tiny stub"]
    eoa --> sc(["safety score<br/>100 โˆ’ risks"])
    px --> sc
    bc --> sc
    own --> sc
    tok --> sc
    st --> sc
    sc --> band{"safety band"}
    band -->|"<= 30"| dg["dangerous"]
    band -->|"31 - 65"| ca["caution"]
    band -->|">= 66"| sf["safe"]
    dg --> plan["execution_plan<br/>approve / block + bounded size"]
    ca --> plan
    sf --> plan

Use it two ways

As an MCP server โ€” for MCP-capable agents:

pip install -r requirements.txt
python sentinel_skill.py          # stdio MCP server exposing risk_check + execution_plan

As a framework Skill (SKILL.md) / CLI โ€” for Claude Code / OpenClaw / Codex style agents:

python sentinel_cli.py <address> <action>                    # verdict (JSON)
python sentinel_cli.py <address> approve --plan --max-risk safe   # risk-gated plan

The CLI exits 0 for safe/caution (or an approved plan) and 2 for dangerous/blocked, so a shell or agent can branch on the exit status alone. See SKILL.md for the skill definition.

Quickstart

Live reads require Foundry (cast) on your PATH (curl -L https://foundry.paradigm.xyz | bash && foundryup). The offline tests and the --synthetic tour need neither Foundry nor network.

python -m unittest test_sentinel   # 34 deterministic offline tests (no network, no Foundry)
python feature_tour.py --synthetic # walk every signal instantly (no network)
python demo_agent.py               # an agent drives the Skill over MCP against live Atlantic
python -c "import pharos_atlantic as p; print('chain_ok:', p.chain_ok())"

Sample output

$ python sentinel_cli.py 0x24f3cd306c85903ca2ccd0ee8dc1c74111151b23 call
{
  "verdict": "caution",
  "score": 65,
  "reasons": ["tiny bytecode (1 bytes) โ€” likely a stub/trap rather than a working contract"],
  "data": { "is_contract": true, "code_size": 1 }
}

Live demo

Drive five Sentinel features as five real Atlantic transactions, one command each:

python demo.py deploy      # deploy a malicious contract -> Sentinel flags it DANGEROUS
python demo.py upgrade     # swap a proxy's logic in one tx -> verdict escalates
python demo.py pause       # pause a contract -> verdict escalates
python demo.py transfer    # execution_plan sends real PHRS only when safe
python demo.py x402        # pay-per-query risk check over x402
python demo.py all         # all five, with a transaction summary

Every run deploys fresh contracts and sends fresh transactions โ€” it's live, not a recording. Full runbook (commands, suggested patter, expected output) in DEMO.md.

Live on Pharos Atlantic

Sentinel integrates with Pharos Atlantic Testnet via live Foundry cast reads:

  • RPC https://atlantic.dplabs-internal.com ยท chainId 688689 ยท explorer https://atlantic.pharosscan.xyz ยท gas token PHRS

Live risk gallery

To prove the engine against real bytecode (not mocks), a spectrum of decoy contracts was deployed on Atlantic, each engineered to trip a different signal. Sentinel reads them live and returns a monotonic safe โ†’ caution โ†’ dangerous ladder โ€” reproduce it yourself with python gallery.py:

Exhibit Action Verdict Score Signal demonstrated
CleanToken transfer ๐ŸŸข safe 0 clean ERC-20, no privileged owner โ€” baseline, no false alarm
MinimalProxy call ๐ŸŸข safe 10 EIP-1167 minimal proxy detected
TinyStub call ๐ŸŸก caution 35 tiny-bytecode stub / trap
ZeroSupplyToken transfer ๐ŸŸก caution 40 zero-supply token trap + EOA owner
UpgradeableProxy call ๐ŸŸก caution 50 EIP-1967 upgradeable + EOA owner + paused
Backdoor call ๐Ÿ”ด dangerous 70 SELFDESTRUCT + unguarded DELEGATECALL + paused

Each verdict above is produced live, on-chain. The address/verdict map lives in fixtures.json; gallery.py re-checks every exhibit and fails on any drift. The Solidity-backed exhibits (CleanToken, ZeroSupplyToken, UpgradeableProxy, Backdoor) have verified source on Pharos Scan โ€” open any address and check the Contract tab; sources are in fixtures/.

Live upgrade attack โ€” Sentinel catches a rug as it happens

The gallery above is static. To prove Sentinel reads live, mutable state โ€” and to demonstrate the exact threat it warns about โ€” a mutable EIP-1967 proxy was deployed pointing at benign logic, then upgraded on-chain to hostile logic in a single transaction. Sentinel read the same proxy address before and after:

Implementation Verdict What Sentinel sees
Before benign logic ๐ŸŸข safe (80) upgradeable โ€” owner can swap the logic after you interact
After (upgrade tx) hostile logic ๐ŸŸก caution (50) + an EOA owner now holds privileged control + the contract is now PAUSED

The proxy address never changed โ€” 0x22Aaโ€ฆd27A โ€” but its implementation, ownership, and pause state did. That is the upgrade-rug vector demonstrated end to end: because Sentinel reads on-chain state at call time, its verdict reflects the swap the moment it lands. The warning it prints before an attack is the risk that becomes the attack.

Live pause flip โ€” Sentinel tracks operational state

A second live mutation, on a plain (non-proxy) contract. It carries a latent SELFDESTRUCT (โˆ’25 โ€” safety 75, still inside the safe band on its own); the operator then pauses it in a single transaction, and Sentinel, reading state at call time, tips to caution:

paused() Verdict What Sentinel sees
Before โ€” ๐ŸŸข safe (75) latent SELFDESTRUCT
After (pause tx) true ๐ŸŸก caution (55) + the contract is now PAUSED

Same contract โ€” 0xE84fโ€ฆ410B โ€” different live state, different verdict.

The gate moves real value

execution_plan is not advisory theatre โ€” it decides whether value actually moves. guarded_transfer.py asks Sentinel, then sends a real PHRS transfer only when the plan is approved:

  • โœ… vetted counterparty โ†’ safe โ†’ approved โ†’ 0.0005 PHRS sent
  • โ›” the live Backdoor fixture โ†’ dangerous โ†’ blocked โ†’ no transaction is signed

The Skill stays read-only; only the agent signs. One approval moved value on-chain; one block stopped it before a transaction existed.

Paid calls via x402

Pharos lists "pay-per-query supplier / supply-chain risk assessment" as a flagship x402 use case โ€” which is exactly what Sentinel is. So risk_check is also exposed behind an x402 paywall: an unpaid request gets 402 Payment Required; the agent pays a micro-transfer on Atlantic; the retry returns the verdict plus the settlement tx_hash.

python sentinel_x402.py   # read-only x402 gate on 127.0.0.1:4021
python x402_demo.py       # 402 -> pay on Atlantic -> 200 verdict -> replay rejected

The gate verifies payment with the same Foundry cast reads Sentinel uses for risk, so the server stays read-only and keyless (only the client sends value) and adds no new dependencies beyond Foundry + the Python stdlib. Full design and the official @x402 SDK path: X402.md.

Verify it yourself

Nothing here is a recording. Sentinel only reads public Pharos Atlantic state, so you can independently confirm both the verdicts and that it never writes.

1. Offline tests โ€” no network, no Foundry:

python -m unittest test_sentinel          # 34 deterministic tests pin the verdict logic

2. Live risk gallery โ€” read-only (needs Foundry cast):

python gallery.py                         # re-checks the 6 deployed fixtures; fails on any drift

Expect a monotonic safe โ†’ caution โ†’ dangerous ladder, all six matching fixtures.json.

3. Check any address yourself:

python sentinel_cli.py 0x75fb8b091A7A88bAF14F23Eac2F33962A4Cdd35D call   # the Backdoor fixture โ†’ dangerous (exit 2)

4. Reproduce Sentinel's reads by hand โ€” proof it just reads public chain data via Foundry, nothing hidden:

RPC=https://atlantic.dplabs-internal.com
# the bytecode Sentinel scans (contains SELFDESTRUCT / unguarded DELEGATECALL):
cast code    0x75fb8b091A7A88bAF14F23Eac2F33962A4Cdd35D --rpc-url $RPC
# the pause flag it reads:
cast call    0x75fb8b091A7A88bAF14F23Eac2F33962A4Cdd35D "paused()(bool)" --rpc-url $RPC
# the EIP-1967 implementation slot of the UpgradeableProxy fixture:
cast storage 0xE7797e15DEb86931d7F7b940684Ed1edc5cC7513 \
  0x360894a13ba1a3210667c828492db98dca3e2076cc3735a920a3ca505d382bbc --rpc-url $RPC

These are exactly the reads documented in references/sentinel.md โ€” Sentinel just folds them into a single verdict. (All are cast call / code / storage; never cast send.)

Security posture

The Skill is intentionally minimal and auditable. It executes only read-only Foundry cast commands (cast code / call / storage / balance / nonce / chain-id, plus cast rpc for tx/receipt lookups) against a single Pharos RPC endpoint โ€” the same execution model the rest of the Skill Engine uses. It holds no private key, never signs or sends a transaction, makes no filesystem writes, and reads no secrets or environment beyond the RPC URL.

Files

File Role
sentinel_skill.py MCP server โ€” tools risk_check, execution_plan
pharos_atlantic.py Pharos Atlantic config + read-only Foundry cast read/introspection helpers
sentinel_cli.py Thin CLI wrapper for SKILL.md / framework agents
SKILL.md Skill definition for Claude Code / OpenClaw / Codex
demo.py Live demo driver โ€” one subcommand per on-chain feature (deploy/upgrade/pause/transfer/x402)
DEMO.md Live demo runbook โ€” commands, suggested patter, expected output
demo_agent.py Demo agent driving the Skill over a real MCP connection against live Atlantic
guarded_transfer.py Agent that sends a real PHRS transfer only when execution_plan approves
feature_tour.py Guided walkthrough of every risk signal
gallery.py Re-checks the live Atlantic risk-gallery fixtures and flags drift
fixtures.json Deployed gallery addresses + expected verdicts
sentinel_x402.py x402 paid-call gate โ€” risk_check behind HTTP 402 (read-only verify)
x402_demo.py Drives the full x402 pay-per-query loop on live Atlantic
X402.md x402 design: native verify-by-RPC + the official @x402 SDK path
references/sentinel.md Sentinel's engine reference file โ€” risk gate + Foundry (cast) read equivalents
references/{query,transaction,contract,script-gen}.md Pharos Skill Engine command references (queries, transactions, contracts, script-gen)
assets/networks.json Pharos network config (Atlantic testnet + mainnet) โ€” engine schema
assets/tokens.json Known token registry (both networks) โ€” engine schema
assets/{erc20,airdrop,templates}/ Engine assets โ€” ERC-20 + airdrop Solidity, script-gen templates
test_sentinel.py 34 offline, deterministic tests
skill.json Sentinel MCP manifest

License

MIT-0 (MIT No Attribution) โ€” free to use, modify, and redistribute. See LICENSE.

Recommended Servers

playwright-mcp

playwright-mcp

A Model Context Protocol server that enables LLMs to interact with web pages through structured accessibility snapshots without requiring vision models or screenshots.

Official
Featured
TypeScript
Magic Component Platform (MCP)

Magic Component Platform (MCP)

An AI-powered tool that generates modern UI components from natural language descriptions, integrating with popular IDEs to streamline UI development workflow.

Official
Featured
Local
TypeScript
Audiense Insights MCP Server

Audiense Insights MCP Server

Enables interaction with Audiense Insights accounts via the Model Context Protocol, facilitating the extraction and analysis of marketing insights and audience data including demographics, behavior, and influencer engagement.

Official
Featured
Local
TypeScript
VeyraX MCP

VeyraX MCP

Single MCP tool to connect all your favorite tools: Gmail, Calendar and 40 more.

Official
Featured
Local
graphlit-mcp-server

graphlit-mcp-server

The Model Context Protocol (MCP) Server enables integration between MCP clients and the Graphlit service. Ingest anything from Slack to Gmail to podcast feeds, in addition to web crawling, into a Graphlit project - and then retrieve relevant contents from the MCP client.

Official
Featured
TypeScript
Kagi MCP Server

Kagi MCP Server

An MCP server that integrates Kagi search capabilities with Claude AI, enabling Claude to perform real-time web searches when answering questions that require up-to-date information.

Official
Featured
Python
E2B

E2B

Using MCP to run code via e2b.

Official
Featured
Neon Database

Neon Database

MCP server for interacting with Neon Management API and databases

Official
Featured
Exa Search

Exa Search

A Model Context Protocol (MCP) server lets AI assistants like Claude use the Exa AI Search API for web searches. This setup allows AI models to get real-time web information in a safe and controlled way.

Official
Featured
Qdrant Server

Qdrant Server

This repository is an example of how to create a MCP server for Qdrant, a vector search engine.

Official
Featured