Skip to content

edycutjong/payguard

Repository files navigation

PayGuard

PayGuard πŸ’‚

The CertiK-grade seatbelt for x402 β€” safe, guarded agent payments on Pharos.

PayGuard β€” GuardianRail blocks 8/8 agent-drain attacks and settles on Pharos

Agent Skill Demo Video On-chain Settled
Pharos Hackathon DoraHacks BUIDL NPM Version


MCP Solidity TypeScript x402 MCP tested with Foundry License: MIT CI CodeQL


πŸš€ Run it β€” for judges

Verify the core claims in 30 seconds β€” zero keys, zero accounts, fully offline:

npm install && npm run bench # β†’ 8/8 attacks blocked
npm run test:guard           # β†’ 20/20 guard-brain edge cases (offline)
forge install foundry-rs/forge-std OpenZeppelin/openzeppelin-contracts && forge test # β†’ 20/20 + fuzz + reentrancy
npm run agent                # β†’ autonomous agent; GuardianRail blocks 5/5 unsafe buys (offline, no key)
npm run mcp                  # β†’ GuardianRail as an MCP server (any MCP client can call the guard)

Want the live on-chain settlement too? It's a real testnet tx you can verify without running anything β€” see Phase 1 on-chain proof. To reproduce it locally (needs a funded Atlantic test wallet):

cp .env.example .env # fill AGENT_PK, FACILITATOR_PK, RECEIVER_ADDRESS (see comments)
forge script script/DeployMockUSDC.s.sol --rpc-url atlantic --broadcast # prints MockUSDC β†’ set USDC_ADDRESS
npm run probe        # prove EIP-3009 settlement on Atlantic
npm run server       # terminal 1 β€” x402-protected resource server
npm run demo         # terminal 2 β€” guarded agent pays end-to-end (settles 0.001 USDC)

πŸ“Έ See it in action

GuardianRail faces 8 attack vectors β€” every unauthorized spend is blocked before the agent ever signs:

  πŸ›‘οΈ  PayGuard Β· GuardianRail β€” Attack β†’ Blocked
β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”¬β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”¬β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”¬β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”
β”‚ (index) β”‚ Attack Vector                   β”‚ Result          β”‚ Guard Code          β”‚
β”œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”Όβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”Όβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”Όβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€
β”‚ 0       β”‚ 1. The Drainer (10,000 USDC)    β”‚ πŸ›‘ BLOCKED      β”‚ MAX_SPEND           β”‚
β”‚ 1       β”‚ 2. The Phish (0xFakeToken)      β”‚ πŸ›‘ BLOCKED      β”‚ INVALID_ASSET       β”‚
β”‚ 2       β”‚ 3. The Rogue Node (bad payTo)   β”‚ πŸ›‘ BLOCKED      β”‚ UNAPPROVED_PAYEE    β”‚
β”‚ 3       β”‚ 4. The Revert (sim fails)       β”‚ πŸ›‘ BLOCKED      β”‚ SIMULATION_FAILED   β”‚
β”‚ 4       β”‚ 5. The Inflator (negative amt)  β”‚ πŸ›‘ BLOCKED      β”‚ MAX_SPEND           β”‚
β”‚ 5       β”‚ 6. The Malformed (float amt)    β”‚ πŸ›‘ BLOCKED      β”‚ INVALID_ASSET       β”‚
β”‚ 6       β”‚ 7. The Clean Run                β”‚ βœ… AUTHORIZED   β”‚ AUTHORIZED          β”‚
β””β”€β”€β”€β”€β”€β”€β”€β”€β”€β”΄β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”΄β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”΄β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”˜
  βœ… TOC/TOU pin β€” 2-entry offer collapsed to the single validated requirement
  βœ… 8/8 vectors handled correctly β€” 100% of unauthorized agent spends blocked.

πŸ€– Skill-to-Agent β€” the guard protecting a live agent

npm run agent turns GuardianRail loose on an autonomous agent. A procurement agent is given a budget and a goal ("assemble a trading-research data bundle") and shops an x402 data marketplace β€” every purchase gated by the shipped createGuardedFetch before any signature. Set ANTHROPIC_API_KEY for a live Claude (claude-opus-4-8) tool-use loop; with no key it runs a deterministic planner (offline, identical every run):

πŸ€– autonomous agent Β· goal: assemble a safe research bundle under 5 USDC/day
   βœ… DataHub       AUTHORIZED β€” charged 0.5 USDC
   πŸ›‘ AlphaDrainer  BLOCKED [MAX_SPEND]          10,000 USDC > 5 USDC per-call cap
   βœ… WeatherWire   AUTHORIZED β€” charged 2 USDC
   πŸ›‘ CheapFeed     BLOCKED [INVALID_ASSET]      spoofed token, not USDC
   πŸ›‘ GhostNode     BLOCKED [UNAPPROVED_PAYEE]   payee not in allowlist
   πŸ›‘ PausedOracle  BLOCKED [SIMULATION_FAILED]  eth_call would revert
   πŸ›‘ NewsFeed      BLOCKED [BUDGET_EXCEEDED]    3 USDC > 2.5 USDC left today
  πŸ’° spent 2.5 / 5 USDC Β· 5 unsafe payments blocked β€” every drain, spoof, rogue-payee,
     revert, and over-budget attempt stopped BEFORE the agent ever signs.

It reuses the shipped guard unchanged β€” the real GuardianRail protecting a real agent loop, not a reimplementation. The EIP-3009 authorization for a blocked buy is never created.

πŸ’‘ The problem & solution

x402 is Pharos's flagship agent-payment rail β€” but it signs and settles whatever a server's 402 PAYMENT-REQUIRED demands. One LLM hallucination or one rogue endpoint can drain an agent's wallet in seconds: no spend caps, no simulation, no asset checks, no escrow.

PayGuard is the safety layer x402 is missing β€” two composable, atomic Skills any Pharos agent imports:

  • 🚦 GuardianRail β€” a pre-flight interceptor that enforces spend caps, asset-spoof protection, payee allowlists, and an eth_call simulation before the agent signs the EIP-3009 authorization.
  • 🏦 AgentVault β€” a minimal, non-upgradeable, CertiK-grade USDC escrow for conditional / milestone payments.
  • πŸ›°οΈ Proven end-to-end β€” a guarded agent really settles USDC on Pharos Atlantic (verifiable tx below).

πŸ› οΈ The two Skills

🚦 Tool 1 β€” GuardianRail (pre-flight payment interceptor)

Nests under the x402 client via createGuardedFetch, so it gates the 402 offer before signing:

const guarded  = createGuardedFetch(fetch, policy, { rpcUrl });
const safeFetch = wrapFetchWithPayment(guarded, client);   // throws AgentSecurityError on violation

Enforces: canonical-asset (anti-spoof) Β· per-call cap Β· daily budget Β· payee allowlist Β· eth_call simulation.

🏦 Tool 2 β€” AgentVault (conditional USDC escrow)

Minimal, non-upgradeable, strict-CEI escrow (SafeERC20 + ReentrancyGuard) for milestone payments: lock(payee, amount, conditionHash, deadline) β†’ release(id, preimage) / refund(id).

πŸ”Œ MCP-native β€” call GuardianRail from any agent runtime

npm run mcp exposes the shipped guard brain (evaluateRequirement, unchanged) as a Model Context Protocol server over stdio, so Claude Desktop, Cursor, or any MCP client can ask "is this x402 payment safe?" before signing β€” no PayGuard-specific code. Tools: evaluate_payment (asset Β· amount Β· payTo β†’ { allowed, code, reason }) and get_policy. Policy is operator-set via env; an agent can only tighten it, never widen it.

mcp-demo

♻️ Reuse & compose (the crucial Phase-1 criterion)

One public API (src/index.ts), three composition seams β€” under the x402 client (createGuardedFetch), as an MCP tool (npm run mcp), or as the pure, sync evaluateRequirement. It's load-bearing, not decorative: remove GuardianRail and the agent signs whatever a server demands.

πŸ—οΈ Architecture (Hybrid facilitator β€” "Option 3")

graph TD
    Agent[Agent] --> Guard["createGuardedFetch(policy)<br/>enforce + eth_call sim (pre-signature)"]
    Guard --> Client["x402 client (EIP-3009 sign)"]
    Guard --> Server["x402 server (/api/data, 402)"]
    Server --> Fac[Facilitator]
    Fac --> Def["default: official Pharos FACILITATOR_URL"]
    Fac --> Fall["fallback: in-process x402Facilitator (self-hosted)"]
    Def --> Settles["settles USDC on Atlantic (688689)"]
    Fall --> Settles
    AV["High-value path: AgentVault.sol"] --> Actions["lock β†’ release(proof) / refund(deadline)"]
Loading

The in-process facilitator implements FacilitatorClient directly, so the self-hosted fallback needs no HTTP-facilitator service β€” the demo is deterministic even if the public facilitator is down.

πŸ† Tracks & sponsor tech β†’ where in the code

For judges β€” exactly which Pharos / sponsor tech PayGuard uses and where to find it.

Track / Tech How PayGuard uses it Where in the code
Pharos x402 (flagship rail) guarded client Β· resource server Β· in-process facilitator settling EIP-3009 src/guardrail.ts Β· src/server.ts Β· src/facilitator.ts Β· src/demo.ts
Phase 1 β€” Skill Anthropic Agent Skill module (the deliverable) SKILL.md
MCP GuardianRail exposed as an MCP server β€” any MCP client can call the guard src/mcp.ts
CertiK Skill Scanner (security standard) minimal SafeERC20 + ReentrancyGuard + strict-CEI escrow Β· 100% test coverage Β· Slither-clean contracts/AgentVault.sol
Pharos Atlantic (688689) real, verifiable on-chain settlement tx 0xfd6e66…

βœ… Proven (reproducible)

$ npm run bench          # GuardianRail β€” offline, deterministic
  βœ… 8/8 vectors handled correctly β€” 100% of unauthorized agent spends blocked.

$ forge test             # AgentVault + MockUSDC β€” OZ v5.6.1 / solc 0.8.28
  [PASS] testFuzz_BalanceMatchesTotalLocked(uint256) (runs: 256)
  [PASS] test_ReentrancyGuardBlocksReentry()        … (+18 more)
  20 tests passed; 0 failed; 0 skipped

$ forge coverage         # contracts/ (AgentVault + MockUSDC)
  100% lines Β· 100% statements Β· 100% branches Β· 100% functions

$ npm run server & npm run demo    # full guarded payment, end-to-end on Atlantic
  βœ… paid + received: { secret: 'PayGuard: safe x402 payment settled on Pharos Atlantic.' }
  # receiver USDC balance 2000 β†’ 3000 on-chain β€” a real settlement, not just a 200

The complete flow is verified end-to-end on Atlantic: a GuardianRail-guarded agent pays the x402 server, the in-process facilitator verifies and settles 0.001 USDC on-chain, and the agent receives the protected content β€” all on @x402/* v2.14.

🎬 Terminal Walkthrough (Step-by-Step)

The entire user flow, contract testing suite, and security boundaries can be run and verified locally via the automated walkthrough script. Below is the step-by-step breakdown of each beat in the walkthrough:

Beat 1: Running GuardianRail Malicious Vector Benchmarks

We run the 8-vector attack suite to verify that token-spoofing, rogue payees, daily budgets, per-call caps, and reverting contracts are blocked pre-flight.

npm run bench

Beat 1: Malicious Benchmarks

Beat 2: Running AgentVault Solidity Contract Suite

We verify the conditional escrow contract and the MockUSDC token tests.

forge test

Beat 2: Foundry Contract Suite

Beat 3: Running Autonomous Agent under GuardianRail

An autonomous agent is given a 5 USDC daily budget and attempts to buy data services. Unsafe services (drains, paused oracles, unapproved payees) are blocked pre-flight.

npm run agent

Beat 3: Autonomous Agent

Beat 4: Initializing Local x402 Resource Server

We spin up the local x402-protected resource server, listening on port 4021.

npm run server

Beat 4: Local Server

Beat 5: Running Guarded E2E Payment Settlement

The guarded agent requests protected data from the server, evaluates the x402 requirement, signs the EIP-3009 payload, and triggers settlement on Pharos Atlantic.

npm run demo

Beat 5: E2E Settlement

Beat 6: Verifying EIP-3009 Settlement on Pharos Atlantic

We check the on-chain transaction logs via Cast to confirm the success status and block number.

cast receipt 0xfd6e66157765066d9ff76068ee9476549153ade951036f3f7863a29f2ffbc253 --rpc-url https://atlantic.dplabs-internal.com/ | grep -E "status|blockNumber"

Beat 6: On-chain Verification

πŸ§ͺ Engineering harness

Layer Tool Status
Type safety tsc --noEmit βœ…
Skill tests GuardianRail attack bench (8 vectors) βœ… 8/8
Guard unit checks npm run test:guard β€” boundary/coercion/precedence/budget edges βœ… 20/20
MCP compatibility GuardianRail exposed as MCP tools (npm run mcp) βœ…
Contract tests Foundry β€” 256-run fuzz + reentrancy + EIP-3009 βœ… 20/20
Contract coverage forge coverage (contracts/) βœ… 100%
Static analysis (Solidity) Slither βœ… 0 high / 0 medium
Static analysis (TypeScript) CodeQL βœ…
On-chain E2E guarded payment settles on Atlantic βœ…
Secret scanning TruffleHog --only-verified (make security-scan) βœ… none committed (.env git-ignored)
Supply chain npm audit + Dependabot βœ…

GitHub Actions runs on every push/PR β€” Foundry (build Β· test Β· gas-snapshot gate) and the TypeScript backend (typecheck Β· lint Β· bench 8/8 Β· guard 20/20), plus a CodeQL workflow (.github/workflows/). Locally, make security-scan adds Slither Β· npm audit Β· license-check Β· TruffleHog.

πŸ” Security β†’ CertiK mapping

Control Attack stopped
targetAsset strict-equality token-spoofing / look-alike phishing
maxSpendPerCall + dailyBudgetRemaining wallet draining via looping / hallucinating agents
allowedPayees allowlist exfiltration to a rogue payee
eth_call pre-flight simulation paying into reverts / paused / blacklisted contracts
SafeERC20 + ReentrancyGuard + CEI reentrancy / non-standard-token drains
minimal contract (no streaming / upgrade / delegatecall) reduced scanner attack surface

πŸ”— Phase 1 on-chain proof (Pharos Atlantic, 688689)

Artifact Value
MockUSDC (EIP-3009) 0xe54205649D6d41Aa9cCdD5667eaDB62f1dFA84AC
Settlement tx 0xfd6e66157765066d9ff76068ee9476549153ade951036f3f7863a29f2ffbc253

Verified on-chain β€” status: success, block 24099194. Inspect it one click on the explorer β†’ Hemera SocialScan, or reproduce it via eth_getTransactionByHash on RPC https://atlantic.dplabs-internal.com/.

πŸ“ Repo layout

SKILL.md                     Anthropic Agent Skill (the submission artifact)
src/guardrail.ts             Tool 1 β€” createGuardedFetch + pure evaluateRequirement
src/simulate.ts              eth_call pre-flight (injectable)
src/rpc.ts                   rate-limit-hardened viem transport (retryingHttp)
src/facilitator.ts           in-process x402Facilitator (self-hosted fallback)
src/server.ts                x402-protected resource server
src/demo.ts                  end-to-end guarded payment
src/agent.ts                 Skill-to-Agent β€” autonomous Claude agent on the guard
src/mcp.ts                   GuardianRail as an MCP server (evaluate_payment, get_policy)
src/probe.ts                 EIP-3009 settlement probe
contracts/AgentVault.sol     Tool 2 β€” conditional escrow (100% coverage)
contracts/MockUSDC.sol       EIP-3009 test USDC (100% coverage)
script/DeployMockUSDC.s.sol  deploy MockUSDC β†’ USDC_ADDRESS
test/AgentVault.t.sol        AgentVault suite (fuzz + reentrancy)
test/MockUSDC.t.sol          EIP-3009 transferWithAuthorization suite
bench/malicious_bench.ts     8/8 attack-vector bench

Stack

@x402/* v2.14 Β· viem v2.52 Β· express Β· Foundry (solc 0.8.28, OZ v5.6.1) Β· TypeScript/ESM. MIT Β© 2026 Edy Cu.