Stellar Hackathon 2026 Β· Agents on Stellar Zero mock data. Real x402 payments. Real Serper.dev Search. Real Groq AI. Real Freighter wallet.
StellarSearch is a pay-per-query web search API for autonomous AI agents. Every search costs 0.001 USDC, settled on Stellar in ~5 seconds using the x402 protocol. No subscriptions, no API keys for the end user β agents pay per request and get real web search results back.
| Layer | Real package / service |
|---|---|
| Payment protocol | @x402/express + @x402/stellar + @x402/core |
| Blockchain | Stellar Testnet (via Horizon API) |
| Facilitator | OpenZeppelin x402 (channels.openzeppelin.com) |
| Wallet connect | @stellar/freighter-api (real Freighter extension) |
| Balances / tx | Stellar Horizon REST API (live, not mocked) |
| Search results | Serper.dev API (real Google search results) |
| AI assistant | groq-sdk Β· Llama 3.3 70B (real Groq API) |
| Frontend | React 18, TypeScript, Tailwind CSS, Framer Motion |
git clone <this-repo>
cd stellar-search
npm install| Key | Where to get it |
|---|---|
STELLAR_RECEIVING_ADDRESS |
Stellar Lab β generate + fund testnet keypair |
SERPER_API_KEY |
serper.dev β free tier: 2.5k queries/month |
GROQ_API_KEY |
console.groq.com/keys β free |
cp .env.example .env
# Fill in the 3 keys above (plus optional variables β see Environment Variables table below)Install the Freighter browser extension, create a testnet wallet, and fund it with USDC at Stellar Lab.
# Terminal 1 β backend
npm run server
# Terminal 2 β frontend
npm run dev
# β http://localhost:5173npm run test:search "Stellar blockchain"All environment variables are read from .env (see .env.example for a template). Variables prefixed with VITE_ are exposed to the browser by Vite; all others are server-side only.
| Variable | Required | Default | Description | Example |
|---|---|---|---|---|
SERPER_API_KEY |
Yes | β | API key for Serper.dev web search. Without this, all search, image, and news endpoints return 500. |
your_serper_api_key_here |
GROQ_API_KEY |
Yes | β | API key for Groq AI (Llama 3). Without this, the AI assistant and search suggestions fail with an auth error. Server prints GROQ: β MISSING on startup. |
gsk_xxxxxxxxxxxxxxxxxxxxxxxx |
STELLAR_RECEIVING_ADDRESS |
Yes | β | Stellar public key that receives 0.001 USDC per query. Without this, the x402 payment middleware has no payTo address and payments fail. Server prints Receiving: β MISSING on startup. |
GDXA3V2LI3VN3GBH5BMOF25QSFJV7S7ZOWMHHQMJRPP4BVORDDRTIIMU |
STELLAR_NETWORK |
No | stellar:testnet |
Stellar network for the server-side x402 middleware. Accepts stellar:testnet or stellar:mainnet. Falls back to testnet if missing. |
stellar:testnet |
VITE_STELLAR_NETWORK |
No | stellar:testnet |
Frontend copy of STELLAR_NETWORK (must be prefixed VITE_ for browser access). Falls back to testnet if missing. |
stellar:testnet |
FACILITATOR_URL |
No | https://www.x402.org/facilitator |
x402 facilitator endpoint for payment settlement. Falls back to the public OpenZeppelin facilitator if missing. | https://www.x402.org/facilitator |
PORT |
No | 3001 |
Express server listen port. Falls back to 3001 if missing. |
3001 |
VITE_SERVER_URL |
No | http://localhost:3001 |
Frontend URL for AI chat backend calls. On Vercel deployments auto-detects ${origin}/api; locally falls back to http://localhost:3001. |
http://localhost:3001 |
Browser (Freighter) β GET /search?q=...
β HTTP 402 + payment requirements
β Sign Soroban auth entry (Freighter prompt)
β GET /search + X-Payment: <signature>
β OpenZeppelin facilitator verifies + settles 0.001 USDC
β 200 OK + Search results
- Agent hits
/searchβ the@x402/expressmiddleware intercepts - Returns
HTTP 402 Payment Requiredwith price + network + payTo address - The x402 client signs a Soroban authorization entry via Freighter wallet
- Retries with
X-Paymentheader containing the signed entry - OpenZeppelin facilitator at
channels.openzeppelin.com/x402/testnetverifies the signature and settles 0.001 USDC on Stellar testnet - Server enforces payment integrity (
src/lib/paymentIntegrity.ts), rejecting replayed or duplicate payloads within the 300-second validity window, and returns search results
To guarantee that each payment identifier authorizes exactly one provider call, StellarSearch tracks consumed payment identifiers across Express (server/index.ts) and Vercel (api/search.ts) runtimes:
- Payload Invalidation: Extracts transaction hashes (or SHA-256 fallback hashes of payment headers) and invalidates consumed payloads for a 300-second window.
- Concurrency Throttling: Rapid parallel requests using identical payment payloads are throttled so only one search query proceeds; concurrent duplicates immediately receive HTTP 402 (
Payment payload already consumed).
sequenceDiagram
autonumber
actor User as User / Agent
participant Browser as Browser
participant Freighter as Freighter Wallet
participant Server as StellarSearch Server
participant Facilitator as x402 Facilitator
participant Horizon as Stellar Horizon
participant Serper as Serper.dev
User->>Browser: Enter search query
Browser->>Server: GET /search?q=...
Server-->>Browser: 402 Payment Required<br/>(price, network, payTo)
Browser->>Freighter: Request signature of<br/>Soroban auth entry
Freighter-->>Browser: Signed payment payload
Browser->>Server: GET /search?q=...<br/>+ X-Payment header
Server->>Facilitator: Verify X-Payment (HTTPFacilitatorClient)
Facilitator->>Horizon: Submit & settle 0.001 USDC tx
Horizon-->>Facilitator: Transaction confirmed (tx hash)
Facilitator-->>Server: Verification + X-Payment-Response
Server->>Serper: POST https://google.serper.dev/search
Serper-->>Server: Real Google search results
Server-->>Browser: 200 OK + results + txHash
Browser-->>User: Display paid search results
stellar-search/
βββ src/ # React frontend
β βββ hooks/
β β βββ useFreighterWallet.ts # Real Freighter + Horizon integration
β β βββ useSearch.ts # Calls real server endpoint
β βββ components/
β β βββ AnimatedBackground.tsx # Canvas animation
β β βββ WalletPanel.tsx # Real Freighter connect + live balances
β β βββ PaymentFlowVisualizer.tsx
β β βββ SearchResults.tsx
β β βββ StatsGrid.tsx # Polls real /health endpoint
β β βββ GroqAssistant.tsx # Real Groq AI chat
β βββ pages/
β β βββ SearchPage.tsx
β β βββ DocsPage.tsx
β β βββ DashboardPage.tsx # Live Horizon tx history
β βββ lib/stellar.ts # Horizon helpers
βββ server/
β βββ index.ts # Express + @x402/express + Serper.dev + Groq
βββ mcp-server/
β βββ index.ts # MCP tools: web_search, ai_summarize, check_balance
βββ scripts/
β βββ test-search.ts # End-to-end test script
βββ .env.example
βββ claude_mcp.json
βββ README.md
// claude_mcp.json
{
"mcpServers": {
"stellar-search": {
"command": "npx",
"args": ["tsx", "./mcp-server/index.ts"],
"env": {
"GROQ_API_KEY": "your_groq_api_key",
"SEARCH_API_URL": "http://localhost:3001"
}
}
}
}Then tell Claude Code: "Search for the latest Stellar x402 examples" β it calls web_search, the server pays via x402, and Claude gets real results.
Coverage is enforced via Vitest + @vitest/coverage-v8 with thresholds for statements, branches, functions, and lines (see vite.config.ts:6).
npm run test # unit tests without coverage
npm run test:coverage # run with coverage + thresholds (CI gate)Reports are generated to coverage/ (text, json, html, lcov). CI uploads the coverage/ artifact and fails if thresholds are not met.
Global thresholds are deliberately modest initially and ratchet upward as payment/wallet/API/MCP/UI tests land:
| Scope | Statements | Branches | Functions | Lines |
|---|---|---|---|---|
| Global | 35% | 30% | 28% | 35% |
src/lib/constants.ts |
90% | 60% | 100% | 90% |
src/lib/stellar.ts |
85% | 75% | 85% | 85% |
src/lib/paymentIntegrity.ts |
90% | 85% | 95% | 90% |
server/corsConfig.ts |
90% | 85% | 95% | 90% |
src/components/search/SearchBar.tsx |
80% | 80% | 90% | 80% |
server/index.ts |
65% | 60% | 65% | 65% |
api/search.ts |
90% | 75% | 80% | 90% |
api/health.ts |
80% | 50% | 100% | 80% |
mcp-server/index.ts |
30% | 20% | 20% | 30% |
src/hooks/useFreighterWallet.ts |
85% | 65% | 90% | 85% |
Ratchet policy: When a module's real coverage exceeds its threshold, bump the threshold in
vite.config.tsin the same PR. Global thresholds ratchet15 β 25 β 35as payment, wallet, API, MCP, and UI behavior moves from untested to tested. Keep Express (server/), Vercel (api/), browser (src/), and MCP (mcp-server/) constants aligned (STELLAR_NETWORK,USDC_CONTRACT,AMOUNT_STROOPS=10000β0.001 USDC).
Coverage verifies the x402 settlement semantics for paid routes (/search, /images, /news): scheme=exact, network=stellar:testnet|mainnet, amount=10000 stroops, asset=C... (Soroban USDC contract, not USDC:ISSUER), payTo=G.... See server/index.ts:104, api/search.ts:48, and mcp-server/index.ts:19.
| Requirement | β |
|---|---|
| Open-source repo + README | β |
| 2β3 min video demo | Record showing: connect Freighter β search β see 402 β payment settles β results |
| Real Stellar testnet transactions | β Every search settles 0.001 USDC via OpenZeppelin facilitator |
| x402 protocol | β
@x402/express + @x402/stellar |
| Addresses explicit demand signal | β "pay-per-query web search instead of monthly subscriptions" |