https://microai-paygate.vercel.app
The backend services (gateway + verifier) are hosted on Render's free tier and sleep after 15 minutes of inactivity. The first request after a quiet period takes 30–50 seconds while both services wake. The web UI shows a brief warm-up banner during this window. Subsequent requests are normal speed.
The deployed stack is Render (Rust verifier + Go gateway) + Vercel (Next.js web) + Upstash (Redis for nonces and signed receipts). All on free tiers — total recurring cost is $0. See DEPLOY.md for the step-by-step deployment guide.
The demo runs on Base Sepolia testnet. A valid EIP-712 signature proves wallet authorization for the payment context; it does not move USDC on-chain. Bring a wallet on Base Sepolia to try the full sign-and-summarize flow.
MicroAI Paygate demonstrates a payment-gated AI microservice stack. A client asks the gateway for an AI summary. If the request is unsigned, the gateway returns HTTP 402 Payment Required with a payment context. The client signs that context with an EVM wallet using EIP-712 typed data and retries the request with X-402-* headers. The gateway verifies the signature through a Rust verifier service, calls the configured AI provider, signs a receipt, stores it, and returns the AI result.
This is a demo and contributor-friendly reference implementation. A valid signature proves wallet authorization for the payment context; it does not prove that USDC moved on-chain.
| Goal | Read |
|---|---|
| Run locally | Getting Started |
| Understand the architecture | Architecture |
| Contribute code or docs | CONTRIBUTING.md |
| Review project rules | RULES.md |
| Report vulnerabilities | SECURITY.md |
| Get support | SUPPORT.md |
| Deploy manually | DEPLOY.md |
| Gateway API contract | gateway/openapi.yaml or GET /docs from the gateway |
| Path | Purpose |
|---|---|
gateway/ |
Go/Gin API gateway on port 3000. Owns CORS, gzip, rate limits, timeouts, Redis cache, receipt storage, AI provider calls, x402 challenge creation, verifier calls, and receipt signing. |
verifier/ |
Rust/Axum service on port 3002. Verifies EIP-712 payment signatures, chain ID, timestamp freshness, and nonce replay for a single verifier instance. |
web/ |
Next.js/Bun frontend on port 3001. Requests summaries, handles 402 payment contexts, switches wallet chain, signs typed data, and retries with X-402-* headers. |
tests/ and run_e2e.sh |
Bun E2E flow covering unsigned challenge, signed retry, verifier acceptance, and replay rejection. |
bench/ |
Reproducible verifier-only micro-benchmark. It does not measure end-to-end latency. |
deploy/, DEPLOY.md, .env.production.example |
Deployment prep for Fly.io gateway/verifier, Vercel web, and Upstash Redis. Real deploy commands are manual. |
.github/workflows/ |
CI for Go, Rust, web, E2E, branch freshness, and Claude review integration. |
flowchart TB
Client["Browser, CLI, or agent client"]
Web["web/ Next.js app :3001"]
Gateway["gateway/ Go Gin API :3000"]
Verifier["verifier/ Rust Axum :3002"]
Redis["Redis 7 receipt store by default\noptional response cache"]
AI["AI provider: OpenRouter or Ollama"]
Wallet["EVM wallet on configured chain\nBase Sepolia default"]
Client --> Web
Client --> Gateway
Web --> Gateway
Web -. "switch chain and sign EIP-712" .-> Wallet
Gateway --> Verifier
Gateway --> AI
Gateway -->|"receipts and cache when configured"| Redis
subgraph Public["Public surface"]
Web
Gateway
end
subgraph Internal["Internal services"]
Verifier
Redis
end
flowchart TB
Browser["Browser wallet UI\nweb/src/app/page.tsx"]
CLI["CLI or agent client"]
subgraph Gateway["gateway/ Go service"]
Gin["Gin router"]
LoggerRecovery["Gin logger and recovery"]
Correlation["Correlation ID middleware"]
Compression["gzip middleware"]
CORS["CORS allowed origins"]
RateLimit["Token bucket rate limiter"]
Timeout["Request timeout middleware"]
Cache["Optional Redis cache middleware\nbypasses unsigned requests; verifies signed cache hits"]
Summarize["POST /api/ai/summarize"]
PaymentContext["Create PaymentContext\nrecipient, token, amount, chainId, nonce, timestamp"]
VerifyClient["Verifier HTTP client\nVERIFIER_URL"]
AIClient["AI provider client\nOpenRouter or Ollama"]
ReceiptSigner["Receipt signer\nserver wallet key"]
ReceiptLookup["GET /api/receipts/{id}"]
Health["/healthz and /readyz"]
end
subgraph VerifierService["verifier/ Rust service"]
BodyLimit["Body size limit"]
Domain["EIP-712 domain\nname, version, chainId, zero verifyingContract"]
Timestamp["Timestamp expiry and clock skew checks"]
Nonce["In-memory nonce replay guard"]
Recovery["ECDSA recovery"]
VerifyRoute["POST /verify"]
end
subgraph Storage["Redis 7"]
Receipts["receipt:{id}\nRedis receipt store by default"]
ResponseCache["ai:summary:{hash}\noptional cached summaries"]
end
MemoryStore["In-memory receipt store\nquick start and tests when RECEIPT_STORE=memory"]
subgraph Providers["AI providers"]
OpenRouter["OpenRouter chat completions"]
Ollama["Local Ollama generate API"]
end
Browser --> Gin
CLI --> Gin
Gin --> LoggerRecovery --> Correlation --> Compression --> CORS --> RateLimit --> Timeout --> Cache
Cache -->|cache miss or disabled| Summarize
Cache -->|signed cache hit| VerifyClient
Summarize -->|missing x402 headers| PaymentContext
PaymentContext --> Browser
PaymentContext --> CLI
Summarize -->|signed retry| VerifyClient --> VerifyRoute
VerifyRoute --> BodyLimit --> Domain --> Timestamp --> Nonce --> Recovery
Recovery -->|valid or structured error_code| VerifyClient
Summarize -->|verified cache miss| AIClient
AIClient --> OpenRouter
AIClient --> Ollama
Summarize --> ReceiptSigner --> Receipts
ReceiptSigner --> MemoryStore
Cache -->|cached response receipt| ReceiptSigner
Cache <--> ResponseCache
ReceiptLookup --> Receipts
ReceiptLookup --> MemoryStore
Gin --> Health
sequenceDiagram
participant C as Client
participant G as Gateway
participant V as Verifier
participant A as AI Provider
participant R as Receipt Store / Optional Cache
C->>G: POST /api/ai/summarize
G-->>C: 402 + paymentContext(recipient, token, amount, chainId, nonce, timestamp)
C->>C: Sign EIP-712 Payment typed data
C->>G: Retry with X-402-Signature, X-402-Nonce, X-402-Timestamp
G->>V: POST /verify with reconstructed context + signature
V-->>G: is_valid + recovered_address or structured error_code
alt Redis response cache hit
G->>R: Read cached summary after verification
else Cache miss or cache disabled
G->>A: Generate summary
A-->>G: Summary text
opt CACHE_ENABLED=true
G->>R: Store cached summary
end
end
G->>G: Sign receipt over request/response hashes
G->>R: Store receipt with TTL
G-->>C: 200 { result } + X-402-Receipt
flowchart LR
Success["Successful signed request"]
Receipt["SignedReceipt JSON"]
Header["Base64 X-402-Receipt header only"]
Store["Receipt store: Redis by default, memory for quick start/tests"]
Lookup["GET /api/receipts/{id}"]
Verify["Client can verify signature with web/src/lib/verify-receipt.ts"]
Success --> Receipt
Receipt --> Header
Receipt --> Store
Store --> Lookup
Header --> Verify
Lookup --> Verify
| Tool | Version | Used by |
|---|---|---|
| Bun | 1.3.13+ recommended |
Root scripts, web install/build, E2E tests |
| Go | 1.24.x |
Gateway |
| Rust | Stable | Verifier |
| Docker | Optional | Compose stack and Redis |
| Redis | Optional for quick start | Required for Docker/production-style Redis receipts |
git clone https://github.com/AnkanMisra/MicroAI-Paygate.git
cd MicroAI-Paygate
bun install
(cd web && bun install)
(cd gateway && go mod download)
(cd verifier && cargo build -q)cp .env.example .envEdit .env before starting the gateway. At minimum:
OPENROUTER_API_KEY: required whenAI_PROVIDER=openrouter.SERVER_WALLET_PRIVATE_KEY: required for signing receipts. Use an unfunded development key locally.RECIPIENT_ADDRESS: recipient address embedded in payment contexts.CHAIN_IDandEXPECTED_CHAIN_ID: must match. The default is84532for Base Sepolia.
The root bun run stack command starts the gateway with RECEIPT_STORE=memory and CACHE_ENABLED=false unless you exported different values in the shell. That means the normal quick start does not require Redis even though production-style receipt storage defaults to Redis.
bun run stackServices:
- Gateway:
http://localhost:3000 - Gateway Swagger UI:
http://localhost:3000/docs - Web:
http://localhost:3001 - Verifier:
http://localhost:3002/health
Docker Compose starts gateway, verifier, web, and Redis. It uses service names inside the Docker network, so the gateway reaches the verifier at http://verifier:3002 and Redis at redis:6379.
cp .env.example .env
docker-compose up --buildCore local variables live in .env.example. Production placeholders live in .env.production.example.
| Variable | Service | Notes |
|---|---|---|
AI_PROVIDER |
Gateway | openrouter by default, ollama for local Ollama experiments. |
OPENROUTER_API_KEY |
Gateway | Required when using OpenRouter. Never commit a real key. |
OPENROUTER_MODEL |
Gateway | OpenRouter model name. Demo docs use z-ai/glm-4.5-air:free unless overridden. |
OLLAMA_URL, OLLAMA_MODEL |
Gateway | Used only when AI_PROVIDER=ollama. |
SERVER_WALLET_PRIVATE_KEY |
Gateway | Signs receipts. Use only unfunded local keys in development. |
RECIPIENT_ADDRESS |
Gateway/Web flow | Embedded in payment contexts returned by the gateway. |
CHAIN_ID |
Gateway/Web flow | EIP-712 chain ID in payment contexts. |
EXPECTED_CHAIN_ID |
Verifier | Verifier enforcement chain. Falls back to CHAIN_ID if unset. |
SIGNATURE_EXPIRY_SECONDS |
Verifier | Signature freshness and nonce retention window. Default 300. |
SIGNATURE_CLOCK_SKEW_SECONDS |
Verifier | Future timestamp grace. Default 60. |
RECEIPT_STORE |
Gateway | redis by default, memory for tests/local experiments. |
REDIS_URL |
Gateway | Required when RECEIPT_STORE=redis or CACHE_ENABLED=true. |
VERIFIER_URL |
Gateway | Required. Where the gateway calls /verify (e.g. http://127.0.0.1:3002 for bun run stack, https://<app>.onrender.com for Render). The gateway refuses to start if unset — no silent loopback fallback. |
CACHE_ENABLED |
Gateway | Optional response cache. Payment verification still runs on cache hits. |
ALLOWED_ORIGINS |
Gateway | Comma-separated CORS origins, no paths or query strings. |
TRUSTED_PROXIES |
Gateway | Comma-separated trusted proxy CIDRs for production IP handling. |
NEXT_PUBLIC_GATEWAY_URL |
Web | Gateway base URL. Browser fetches /api/ai/summarize and /api/receipts/:id here. |
NEXT_PUBLIC_EXPECTED_CHAIN_ID |
Web | Chain ID the wallet widget targets. Must match the gateway's CHAIN_ID. Default 84532. |
NEXT_PUBLIC_EXPECTED_CHAIN_NAME |
Web | Display name paired with the chain ID — used by the wallet widget's "Switch to " button, hero headline, stat bar, and page title. Must be set when CHAIN_ID is overridden (e.g. Base for 8453). |
NEXT_PUBLIC_PAYMENT_AMOUNT |
Web | Pre-challenge fee label shown in the summarize form + stat bar. Display only — the gateway's payment context controls the actual signed amount. Default 0.001. |
NEXT_PUBLIC_PAYMENT_TOKEN |
Web | Token symbol paired with the amount. Default USDC. |
| Area | Command | Notes |
|---|---|---|
| Gateway tests | cd gateway && go test -v ./... |
Uses miniredis for Redis behavior where needed. |
| Gateway vet | cd gateway && go vet ./... |
Run for Go changes. |
| Verifier tests | cd verifier && cargo test |
Covers EIP-712, chain ID, timestamp, and nonce behavior. |
| Verifier lint | cd verifier && cargo fmt -- --check && cargo clippy -- -D warnings |
Run for Rust changes. |
| Web lint/build/typecheck | cd web && bun run lint && bun run build && bun run test |
bun run test is tsc --noEmit. |
| E2E | bun run test:e2e |
Starts gateway/verifier. Requires OPENROUTER_API_KEY for default OpenRouter startup path. |
| All unit tests | bun run test:unit |
Gateway plus verifier tests. |
Do not use bun test by itself for the project E2E flow. It runs Bun's test runner without starting services.
The gateway serves OpenAPI at GET /openapi.yaml and Swagger UI at GET /docs.
| Endpoint | Purpose |
|---|---|
GET /healthz |
Liveness check for the gateway process. |
GET /readyz |
Readiness check for verifier, active AI provider, Redis when required, and gateway self metrics. |
POST /api/ai/summarize |
Payment-gated text summarization endpoint. |
GET /api/receipts/{id} |
Fetch a stored signed receipt until its TTL expires. |
Signed retries to POST /api/ai/summarize must include:
X-402-Signature: <wallet signature>
X-402-Nonce: <nonce from paymentContext>
X-402-Timestamp: <timestamp from paymentContext>Successful summarize responses return:
{
"result": "AI summary text..."
}The signed receipt is returned in the X-402-Receipt response header as base64-encoded SignedReceipt JSON.
The verifier micro-benchmark lives in bench/. It measures only the Rust /verify endpoint. It does not measure gateway latency, wallet signing, Redis, OpenRouter, web UI, or the full x402 flow.
Only cite numbers from committed bench/RESULTS-*.txt files. The latest committed run is bench/RESULTS-2026-05-13.txt:
| Metric | Result |
|---|---|
| Requests/sec | 1526.94 |
| p99 latency | 85.45ms |
Current reruns must generate enough one-time signed payloads because verifier nonces are replay-protected.
- A valid signature is not on-chain settlement. The verifier proves authorization, not that USDC moved.
- Verifier nonce replay protection is in memory for one verifier instance. Multi-replica production needs a shared nonce store.
- Gateway rate limiting is per process. Horizontal scaling needs distributed limits.
RECEIPT_STORE=redisis production-style and restart-safe.RECEIPT_STORE=memoryis for tests and local experiments.- The demo defaults to Base Sepolia (
84532). Multi-chain support would require dynamic EIP-712 domain and config updates across gateway, verifier, web, tests, and docs. - Free OpenRouter demo models keep cost low but may produce weak summaries.
Please read CONTRIBUTING.md before opening a pull request. Security-sensitive changes need extra care around EIP-712 parity, timestamps, nonces, chain IDs, receipts, CORS, rate limits, and secret handling.
For normal support, use SUPPORT.md. For vulnerabilities, use SECURITY.md and do not disclose exploit details publicly.
MicroAI Paygate is released under the MIT License.
