Ag402 v0.1.0 -- Payment infrastructure for the AI Agent economy. Powered by the Open402 standard.
Three-layer monorepo: open402 (protocol) + ag402-core (engine) + ag402-mcp (gateway adapter).
398 tests passing. Lint clean (ruff). E2E demo working. Security audited (P0-P2 fixed). CI/CD configured.
Devnet tested (17 on-chain tests + 28 resilience tests + 4 devnet timing tests). Retry + failover integrated.
V1 Focus: 100% Python ecosystem support (generic Python agents via httpx/requests). Node.js/desktop integrations planned for V2.
- Added
_check_port_available()to detect backend port availability - If backend is not running, auto-starts a Starlette Demo API in background (returns JSON: service, message, path, params, price)
- Runs via
threading.Thread(daemon=True)with uvicorn - Custom request logging middleware replaces uvicorn default access logs (402→💰, 200→✓, 500→✗, with latency)
- After startup, shows "buyer view" hint:
ag402 pay <gateway_url>
- No longer uses middleware black box — manually executes each protocol step
- 6-step visual flow: ①Wallet setup → ②Send request (show 402) → ③Receive payment challenge (chain/token/amount/address) → ④Auto-pay (tx_hash) → ⑤Retry with proof (proof→200) → ⑥Settlement (elapsed time + balance change)
- Added
_print_response_body()helper: JSON pretty-print (10-line truncation), HTML/text (6-line truncation) - Uses
open402library:parse_www_authenticate,build_authorization,X402PaymentProof
- All commands use numbered steps (①②③④⑤) with clear labels
- Colorized status codes, JSON pretty-printing
- Non-JSON responses truncated with ellipsis
+---------------------------------------------------+
| ag402-mcp HTTP gateway adapter |
| (adapters/mcp) FastAPI reverse proxy + x402 |
+---------------------------------------------------+
| ag402-core Payment engine |
| (core/) Wallet, middleware, CLI, |
| monkey-patch, proxy, runners |
+---------------------------------------------------+
| open402 Protocol standard |
| (protocol/) Spec, headers, negotiation |
| Zero dependencies |
+---------------------------------------------------+
- spec.py:
X402PaymentChallenge,X402PaymentProof,X402ServiceDescriptor, JSON Schema. Amount validation rejects NaN/Infinity/zero/negative. Header serialization sanitizes CR/LF/quote to prevent injection. - headers.py: Parse/build
WWW-Authenticate: x402andAuthorization: x402. Extension headers (X-Service-Hash, X-Agent-ID) parsed and passed through - negotiation.py:
Accept-x402-Versionnegotiation with graceful degradation - Zero dependencies -- pure Python protocol types
- Frozen
X402Configdataclass - Auto-loads
~/.ag402/.envon startup (zero-config for users) - All budget limits configurable via environment variables with hard ceilings:
X402_DAILY_LIMIT(default $10, ceiling $1000)X402_PER_MINUTE_LIMIT(default $2, ceiling $10)X402_PER_MINUTE_COUNT(default 5, ceiling 50)X402_CIRCUIT_BREAKER_THRESHOLD(default 3, ceiling 20)X402_CIRCUIT_BREAKER_COOLDOWN(default 60s, ceiling 3600s)
RunModeenum: PRODUCTION / TEST- Default mode changed to TEST (v0.1.0) -- zero-config developer onboarding
- PBE wallet encryption support via
AG402_UNLOCK_PASSWORD
- Zero-dependency
.envfile parser (handles quotes, equals in values, inline comments) - Robust: unparseable lines logged as warnings, never crashes
load_dotenv()/save_env_file()/set_env_value()/parse_env_file()- 30 unit tests covering edge cases
- Interactive terminal UI for first-time setup
- Role selection: consumer (buy API) / provider (sell API) / both
- Full encryption flow even in test mode (security perception)
- Generates
~/.ag402/.envand~/.ag402/wallet.key - Auto-deposits test funds after setup
ag402_core.enable()/disable()/enabled()context manager- Patches
httpx.AsyncClient.sendandrequests.Session.send - Non-402 responses pass through completely untouched (no exception swallowing)
- Original exception stacks preserved exactly
- Handles existing event loops via ThreadPoolExecutor + asyncio.run()
- 11 unit tests
- HTTP forward proxy on
127.0.0.1:14020 - Intercepts 402 → auto-pay → retry with proof
- HTTPS CONNECT tunnels passed through as-is (no MITM)
- SSRF protection: Blocks CONNECT to private/loopback/link-local/reserved IPs; restricts to ports 80/443/8080/8443
- Used by
ag402 runfor HTTP-level interception - Note: HTTPS auto-pay requires Python SDK injection (
ag402.enable()), proxy mode only handles HTTP
- base.py:
AgentRunner— launches child process with env injectionsitecustomize.py+PYTHONPATHinjection for Python processes (works for scripts AND interactive shells)HTTP_PROXYfor HTTP traffic
- Wraps CLI with human-readable error messages
- Pattern-matched errors → "what went wrong" + "what to do next"
- Raw tracebacks only shown with
AG402_DEBUG=1
- agent_wallet.py: SQLite (WAL mode + 10s timeout) + asyncio.Lock. deposit/deduct/rollback/get_balance/get_daily_spend
- models.py: Transaction dataclass
- faucet.py: X402_MODE=test auto-deposits 100 virtual USD
- payment_order.py:
PaymentOrder+PaymentOrderStore+OrderState6-state machine (CREATED → LOCAL_DEDUCTED → CHAIN_BROADCASTED → DELIVERING → SUCCESS / REFUNDED). Strict uni-directional transitions with SQLite persistence.
- base.py:
BasePaymentProviderABC +PaymentResultdataclass.verify_payment()supportsexpected_senderto prevent third-party tx_hash reuse.confirmation_statusfield distinguishes "confirmed" vs "sent" (confirmation timed out). - registry.py:
PaymentProviderRegistry— lazy-loading provider resolution (auto/mock/solana/stripe-v2). Passesrpc_backup_urlfrom config to SolanaAdapter. - solana_adapter.py:
SolanaAdapter(real) +MockSolanaAdapter(test). Configurableconfirmation_level("confirmed"/"finalized") for chain reorg protection. Usesround()for lamport conversion to avoid float precision errors. Retry + Failover: RPC calls (get_latest_blockhash,send_transaction) are wrapped withretry_with_backoff(exponential backoff, configurablemax_rpc_retries). On retry exhaustion, auto-failovers torpc_backup_urlviaMultiEndpointClient. Returnsconfirmation_status("confirmed" | "sent") inPaymentResult. - retry.py:
retry_with_backoff()— async retry with exponential backoff (configurable max_retries, base_delay, max_delay).MultiEndpointClient— multi-RPC endpoint manager with sequential failover and primary reset. Both integrated intoSolanaAdapterfor production use.
- models.py:
PrepaidCredentialdataclass. 5 package tiers (Starter 100c/3d → Enterprise 10k c/730d). UTC-aware datetimes with_utc()backward-compat helper. HMAC-SHA256 signature coversbuyer|package|expires_at(excludesremaining_calls— decrements locally) - client.py: Buyer-side JSON store at
~/.ag402/prepaid_credentials.json. Atomic write viatempfile+os.replace(). Allfrom_dict()calls catch(KeyError, ValueError, TypeError)for corrupt-entry resilience.rollback_call()capped at original package limit to prevent reject-loop inflation - verifier.py (NEW in P0-2): Stateless
PrepaidVerifierfor seller/gateway side. 5-step: parse → seller_address match → expiry → remaining_calls > 0 → HMAC.hmac.compare_digest()for constant-time comparison.remaining_callsexcluded from HMAC (buyer manages locally) - Prepaid fast-path in
x402_middleware._try_prepaid(): 1ms local verify vs 500ms+ on-chain.check_and_deduct+rollback_callboth under_payment_lock(TOCTOU + concurrent-rollback safe). 429/5xx upstream → rollback (buyer gets service or refund)
- x402_middleware.py: Core loop: Intercept 402 -> parse -> budget -> prepaid fast-path -> deduct -> pay -> retry -> rollback. Prepaid path (
_try_prepaid): check local credential under lock → attachX-Prepaid-Credentialheader → on seller rejection or network error, rollback under lock and fall through to on-chain. Dual-mode fallback (non-x402 402 -> Bearer API key). Replay header injection. Integrated PaymentOrder state machine for payment lifecycle tracking (CREATED → LOCAL_DEDUCTED → CHAIN_BROADCASTED → DELIVERING → SUCCESS/REFUNDED). Point-of-no-return semantics: after chain broadcast, local deductions are never rolled back. Budget-deduct lock: asyncio.Lock serializes budget check + deduct to prevent TOCTOU race. - budget_guard.py: Single-tx limit + per-minute limit + daily limit + circuit breaker + balance check. All configurable via env with hard ceilings.
- key_guard.py:
PrivateKeyFilterlogging filter +install_key_guard() - wallet_encryption.py: PBE encryption (PBKDF2-HMAC-SHA256 + Fernet/AES). Encrypt/decrypt private keys with user password. Docker support via env var. Atomic file write via tempfile + os.replace() to prevent crash-induced corruption.
- rate_limiter.py: In-memory sliding-window rate limiter
- replay_guard.py:
ReplayGuard(timestamp + nonce validation, OrderedDict cache with pruning, nonce length limit, cache-full flood protection) +PersistentReplayGuard(SQLite-backed tx_hash deduplication with atomic INSERT OR IGNORE, lazy init with_ensure_db()on all public methods includingprune()) +generate_replay_headers() - challenge_validator.py: Pre-payment challenge validation (URL, amount, address, token, trust). Uses
ipaddressmodule for complete loopback detection (IPv4/IPv6/0.0.0.0).
- auth.py:
PaymentVerifierfor server-side proof validation
- 19 commands:
setup,init,run,env,serve,pay,prepaid,upgrade,help,status,balance,history,tx,config,info,doctor,demo,export,mcp,mcp-config,install - Prepaid management (NEW in P0-3):
ag402 prepaid status/ag402 prepaid purge/ag402 prepaid buy <gateway_url> <package_id> - Interactive setup wizard:
ag402 setup(role selection + encryption + budget) - Agent integration:
ag402 run -- python agent.py - Config management:
ag402 env show/set - Provider mode:
ag402 serve(auto-starts built-in demo backend + custom request logging,--host 0.0.0.0for Docker) - Buyer mode:
ag402 pay <url>(6-step x402 negotiation visualization) - Production migration:
ag402 upgrade - Beautiful help:
ag402 help(categorized with usage hints) - Friendly errors: human-readable messages with "next step" guidance
- Colorized output: ANSI colors with auto-detection, progress bars, dashboard layout
- Cross-platform: macOS, Linux, Windows (respects
NO_COLOR/FORCE_COLOR)
- gateway.py: HTTP gateway adapter with CLI entry point (
ag402-gateway) - Wraps any API with x402 payment verification
- Prepaid fast-path (NEW in P0-2):
X-Prepaid-Credentialheader processed before on-chain auth. Valid credential → proxy immediately (1ms). Invalid/expired/depleted → 402 with standard x402 challenge so buyer falls back to on-chain. Enabled via--prepaid-signing-keyCLI arg orAG402_PREPAID_SIGNING_KEYenv var - Prepaid purchase endpoints (NEW in P0-3):
GET /prepaid/packages(public, 5 tiers) +POST /prepaid/purchase(verify tx_hash → issue signed credential JSON).tx_hashvalidated[A-Za-z0-9_-]{1,128}(header injection prevention). Credential returned to buyer, not stored on seller. - Production mode safety: Refuses to start without real verifier when
X402_MODE=production; prominent test mode warning banner - GET /health endpoint: Returns JSON with status, mode, uptime, and metrics counters (requests_total, payments_verified, payments_rejected, replays_rejected, challenges_issued, proxy_errors, prepaid_verified, prepaid_rejected)
- Header whitelist: Only forwards known-safe headers (accept, content-type, user-agent, etc.) to upstream; blocks Cookie, X-Forwarded-For, Connection
- tx_hash deduplication: Atomic
INSERT OR IGNOREin persistent SQLite-backedPersistentReplayGuard(survives restarts). Eliminates TOCTOU race in concurrent requests. - Shared httpx client: Created/closed via lifespan, with fallback per-request client for non-lifespan usage
pip install ag402-core
↓
ag402 setup ← Interactive wizard (role/mode/encryption/limits)
↓
ag402 demo ← Live demo
↓
ag402 run -- python my_agent.py ← Integrate your agent
↓
ag402 upgrade ← Test → production migration
No need to: read more than 10 lines of README, edit config files, or manually set environment variables.
| Agent Framework | Compatibility | Method |
|---|---|---|
| Generic Python Agent | ★★★★★ | ag402 run -- python xxx |
| Python SDK | ★★★★★ | ag402_core.enable() — two lines |
| LangChain / AutoGen / CrewAI | ★★★★★ | httpx/requests based, auto-compatible |
- Three-layer monorepo: Protocol (zero deps) -> Engine -> Adapters
- x402 Compatible: Standard Coinbase x402 fields + extension headers (X-Service-Hash, X-Agent-ID)
- Frozen Config: Immutable after startup
- Hard ceilings: All env-configurable limits have hardcoded upper bounds for safety
- Adapter pattern: BasePaymentProvider -> Solana now, Stripe/Coinbase/Lightning later
- Lazy loading: Heavy deps (solana-py) only imported when needed
- Auto-rollback: Payment or retry failure -> wallet deduction reversed
- Sub-wallet only: Auto-generated, never import main wallet
- Replay defense: Timestamp window + nonce uniqueness (server-side opt-in)
- Dual-mode: x402 payment OR Bearer API key fallback for non-x402 services
- Test-first default: New installs default to TEST mode for safe experimentation
- PBE encryption: Private keys encrypted at rest with PBKDF2 + AES
- Monkey-patch transparency: Non-402 responses pass through untouched, original exceptions preserved. Stream-safe: never reads body of non-402 responses
- .env zero-dependency: Custom parser (no python-dotenv), graceful on malformed lines
- Test mode = full encryption: Even test mode walks through encryption flow for security perception
- V1 Python-only focus: 100% Python ecosystem, Node.js/desktop planned for V2
- sitecustomize injection: Replaced PYTHONSTARTUP (interactive-only) with sitecustomize.py + PYTHONPATH for reliable script injection
- tx_hash anti-replay: Gateway tracks consumed tx_hashes via OrderedDict with FIFO eviction to prevent payment replay attacks
- SQLite WAL mode: Wallet DB uses WAL journal mode + busy_timeout for multi-process safety
- Thread-safe circuit breaker: BudgetGuard uses threading.Lock for class-level state
- serve auto-backend:
ag402 serveauto-starts a built-in Starlette demo API if no backend is running on the target port - pay 6-step visualization:
ag402 paymanually executes each x402 protocol step with numbered output, replacing middleware black-box - Custom request logging:
serveuses middleware-based request logging (402💰/200✓/500✗ + latency), suppressing uvicorn default access logs - PaymentProviderRegistry: Dynamic provider resolution based on config mode — eliminates hardcoded adapter selection
- PaymentOrder state machine: Strict 6-state lifecycle (CREATED → LOCAL_DEDUCTED → CHAIN_BROADCASTED → DELIVERING → SUCCESS/REFUNDED). Point-of-no-return after chain broadcast ensures local deductions are never incorrectly rolled back.
- PersistentReplayGuard: SQLite-backed tx_hash deduplication that survives process restarts. Lazy init (
_ensure_db()) on all public methods (includingprune()). Demo cleanup closes persistent guard to prevent orphaned aiosqlite threads. - RPC Retry + Failover:
SolanaAdapterwraps critical RPC calls (get_latest_blockhash,send_transaction) withretry_with_backoff(exponential backoff). On retry exhaustion, auto-failovers to backup RPC viaMultiEndpointClient. Resets to primary after successful send. - confirmation_status in PaymentResult:
"confirmed"= confirmed at requested level,"sent"= tx sent but confirmation timed out (may still succeed on-chain). Eliminates ambiguity whensuccess=Truebut confirmation was incomplete.
Full security audit performed. All P0 (critical) and P1 (high) issues fixed. All P2 (medium) issues fixed. P3 (low) items deferred to V2.
| ID | Issue | Fix | File |
|---|---|---|---|
| P0-1.1 | verify_payment() missing sender check — stolen tx_hash attack | Added expected_sender param; _verify_sender() checks pre_token_balance decrease |
solana_adapter.py, base.py, auth.py |
| P0-1.2 | Gateway defaults to test mode (no chain verification) | Production mode raises ValueError without verifier; prominent test mode warning | gateway.py |
| P0-1.3 | tx_hash dedup TOCTOU race — concurrent double-spend | Atomic INSERT OR IGNORE + rowcount check |
replay_guard.py |
| P0-1.4 | Forward Proxy CONNECT tunnel — unrestricted SSRF | Block private/loopback/link-local IPs; restrict to ports 80/443/8080/8443 | forward_proxy.py |
| P0-1.5 | int(amount * 1e6) float truncation |
round(amount * 1_000_000) |
solana_adapter.py |
| ID | Issue | Fix | File |
|---|---|---|---|
| P1-2.1 | amount_float accepts NaN/Infinity | Validates with math.isnan/math.isinf, rejects zero/negative |
spec.py |
| P1-2.2 | Header injection via CR/LF in challenge fields | _HEADER_UNSAFE_RE sanitization in to_header_value() |
spec.py |
| P1-2.3 | No password strength enforcement for wallet encryption | Minimum 8-character password enforced in encrypt_private_key() |
wallet_encryption.py |
| P1-2.4 | Wallet file non-atomic write — crash corruption | tempfile.mkstemp() + os.replace() |
wallet_encryption.py |
| P1-2.5 | Budget check → deduct TOCTOU | asyncio.Lock serializes budget_check + deduct |
x402_middleware.py |
| P1-2.6 | No health check endpoint | GET /health + metrics counters |
gateway.py |
| P1-2.7 | No request rate limiting on Gateway | IP-based sliding-window rate limiter (default 60 req/min), returns HTTP 429 | gateway.py |
| ID | Issue | Fix | File |
|---|---|---|---|
| P2-3.1 | Only "confirmed" commitment — reorg risk | Configurable confirmation_level param ("confirmed"/"finalized") |
solana_adapter.py |
| P2-3.3 | Nonce cache flooding / oversized nonces | _MAX_NONCE_LENGTH=128 + cache-full rejection after prune |
replay_guard.py |
| P2-3.4 | Gateway forwards all headers (Cookie, X-Forwarded-For) | Whitelist-only forwarding (accept, content-type, user-agent, etc.) | gateway.py |
| P2-3.5 | localhost check misses ::1, 0.0.0.0, 127.x.x.x range | _is_local_address() via ipaddress module |
challenge_validator.py |
| P2-3.6 | No health check or observability | GET /health + metrics counters (requests, verified, rejected, errors) |
gateway.py |
| P2-3.7 | Forward Proxy zero test coverage | 39 tests: SSRF, ports, lifecycle, nonce flood, headers, health, localhost | test_v1_p2_security_fixes.py |
- Dependency version pinning (
>=→==+ lockfile) - Docker secrets / vault for password management
- Key Guard for base64/hex-encoded private keys
bytearrayinstead ofstrfor in-memory private keys- MockSolanaAdapter negative balance protection
| Package | Tests | Coverage | Status |
|---|---|---|---|
| open402 (spec, headers, negotiation) | 27 | 100% | PASS |
| ag402-core: wallet | 10 | 96% | PASS |
| ag402-core: payment + registry | 18 | 94%+ | PASS |
| ag402-core: middleware + stateful | 16 | 97% | PASS |
| ag402-core: gateway | 3 | 88% | PASS |
| ag402-core: security (replay, key_guard, rate_limiter) | 62 | 98%+ | PASS |
| ag402-core: payment_order (state machine) | 20 | 98% | PASS |
| ag402-core: CLI (enhanced) | 61 | 75% | PASS |
| ag402-core: env_manager | 30 | 98% | PASS |
| ag402-core: monkey-patch | 11 | 90% | PASS |
| ag402-core: budget_enhanced | 12 | 95% | PASS |
| ag402-core: decimal_precision | 4 | 100% | PASS |
| ag402-core: phase4 (integration) | 22 | 90% | PASS |
| ag402-core: solana_enhanced | 10 | 94% | PASS |
| ag402-core: solana_resilience (mock) | 28 | 96% | PASS |
| ag402-core: devnet on-chain | 17 | -- | PASS |
| ag402-core: devnet resilience + timing | 4 | -- | PASS |
| ag402-core: payment_verifier_amounts | 5 | 95% | PASS |
| ag402-core: v1 security audit (P0/P1) | 40 | 98% | PASS |
| ag402-core: v1 security audit (P2) | 39 | 96% | PASS |
| ag402-mcp: adapter tests | 5 | -- | PASS |
| Total | 447 | 92%+ | ALL PASS |