Pay-Per-Compute GPU Marketplace — submit a workload, get the right GPU picked for you, and pay only for that job with USDC on Algorand Testnet via x402.
GPU execution is simulated. Payment is real: every completed job produces a genuine, verifiable Algorand Testnet transaction.
- You describe a workload — in a form or in plain English.
- ComputeX scores every GPU in the marketplace on VRAM compatibility, price, execution time, reliability and availability, and recommends one.
- You click Pay & Run.
- The server replies 402 Payment Required with a price derived from your workload.
- The x402 client signs a USDC transfer, retries, the facilitator verifies and settles on Algorand Testnet.
- The job runs (simulated) and you get the result plus the real transaction ID.
Prerequisites: Node 20 or newer (verified on Node 25.9 / npm 11.12) and a terminal. No database, no Docker, no GPU required.
From the repository root — this installs both packages:
npm run setupEquivalent to installing each package by hand:
cd server && npm install
cd ../client && npm installcd computex/server
npm run keygen # run twice: once for the payer (buyer), once for the payee (provider)cd computex
cp .env.example .envFill in:
| Variable | Purpose |
|---|---|
PAYEE_ADDRESS |
GPU provider address — receives USDC |
PAYER_MNEMONIC |
Buyer's 25-word mnemonic (server-side only) |
PAYER_PRIVATE_KEY |
Alternative to the mnemonic: base64 64-byte secret key |
PAYEE_MNEMONIC |
Optional, only used by npm run optin |
X402_FACILITATOR_URL |
Defaults to https://facilitator.goplausible.xyz |
ALGORAND_NETWORK |
Leave blank — discovered from the facilitator at startup |
ALGOD_URL / INDEXER_URL |
Default to AlgoNode Testnet |
PORT, RESOURCE_BASE_URL, CORS_ORIGINS |
Local wiring |
.env is gitignored. The payer key never leaves the server.
Both the payer and the payee must be opted in to USDC (ASA 10458941) — an Algorand account cannot send or receive an ASA before opting in.
This applies to the payee as much as the payer: the provider account cannot receive USDC until it has opted in, so skipping it fails at settlement, after the payment has already been signed.
Do these in order — an opt-in is itself a transaction, so an account with 0 ALGO cannot opt in.
- Test ALGO for both addresses — https://bank.testnet.algorand.network/ (~0.5 ALGO each covers fees and minimum balance).
- Opt both into USDC:
cd server && npm run optin
- Test USDC for the payer — https://faucet.circle.com/ (select Algorand Testnet).
- Verify — this is the gate; do not move on until it passes:
npm run check:payer # exits 0 when the flow is ready to run
check:payer prints both balances and ends in one of two states:
READY — the x402 flow should work. # good, continue
NOT READY — fix the items above. # read the per-account lines
ALGO: 0 means that account was never funded; USDC: not opted in means step 2 has not
taken effect for it. Both must be clear on both accounts.
Transaction fees are sponsored by the facilitator's fee payer, so the payer only needs enough ALGO for its minimum balance.
Two terminals, both from the repository root:
# terminal 1 — API
npm run dev:server # http://localhost:4021
# terminal 2 — UI
npm run dev:client # http://localhost:5173Then open http://localhost:5173. The Vite dev server proxies /api to the backend, so the browser stays same-origin.
The server prints its resolved configuration on boot — the network line confirms it discovered Algorand Testnet from the facilitator:
[x402] Algorand network: algorand:SGO1GKSzyE7IEPItTxCByw9x8FmnrCDexi9/cOUJOiI=
[x402] Facilitator: https://facilitator.goplausible.xyz
[x402] Paying to: <your PAYEE_ADDRESS>
ComputeX server listening on http://localhost:4021
Two checks that need no funded account — they confirm the server and the payment gate are live:
curl http://localhost:4021/api/health
curl -i -X POST http://localhost:4021/api/compute \
-H 'content-type: application/json' \
-d '{"workload":"image-generation","model":"sdxl","images":5,"gpu":"rtx-4090"}'Health returns "paymentMode": "real". The second must return 402 Payment Required with a
PAYMENT-REQUIRED header — that is the x402 gate refusing unpaid work, and it is the correct
result, not an error.
On Windows PowerShell,
curlis an alias forInvoke-WebRequestand will not accept these flags. Usecurl.exeexplicitly, or run the commands from Git Bash / WSL.
Then run the full paid path end to end, which requires check:payer to have passed:
npm run test:flow # 402 -> sign -> verify -> settle -> confirm tx via the indexer- Open http://localhost:5173 → Agent Mode.
- Type:
Generate 5 SDXL images under $0.20, prioritize balanced performance. - Interpret & Recommend → shows the extracted requirements, each traced back to your words.
- RTX 4090 is recommended, with the full scored field beneath it.
- Pay & Run → watch the x402 state machine:
402 Payment Required → Payment Signed → Payment Verified → GPU Job Running → Job Completed → Payment Settled - The result panel shows ✓ Payment Settled, the cost, the network, and the real transaction ID.
- View Transaction opens the transaction on the Testnet explorer.
- Jobs shows the run in history with a View TX link; Providers shows the revenue it earned.
Click Connect Pera in the header and the browser performs the x402 flow itself: it takes the 402, builds the USDC transfer, sends it to your phone for approval, and retries. The server holds no key on this path.
With no wallet connected, jobs are charged to the shared server-side payer instead. The panel under Pay & Run always says which account is about to be charged.
The account must be on Algorand Testnet, hold a little ALGO, and be opted in to USDC (ASA 10458941) with a balance — the same three requirements as the server payer, for the same reasons.
The fastest route for a demo is to import the payer account you already funded: in Pera, Add account → Import → Recover from passphrase, then paste the 25 words from PAYER_MNEMONIC in your .env. It is already funded and opted in. Enable Testnet under Settings → Developer Settings → Node Settings.
Otherwise create a fresh account in Pera and take it through the same funding steps as step 4.
| Piece | What it does |
|---|---|
client/src/wallet.ts |
Adapts Pera to ClientAvmSigner, the two-member interface @x402/avm expects |
client/src/walletPay.ts |
The x402 flow in the browser — same events as the server payer, so the UI is shared |
GET /api/trace/:id |
Streams job stages to the wallet path |
That last one exists because the x402 middleware buffers the protected route's body so it can settle afterwards, which makes streaming out of /api/compute impossible. The browser opens the trace stream first and passes the same id on its POST, so the state machine still animates live. npm run test:trace asserts exactly that — including that stages arrive spread over the job's runtime rather than dumped at the end.
The whole app ships as one service: the server serves the built frontend, so the browser calls /api on its own origin — no CORS, no proxy, one URL.
npm run build # installs both packages and builds the client
npm start # serves API + UI on $PORT (default 4021)It must run as a long-lived server, not serverless: the job store, the stage event bus and the NDJSON progress stream are all in-process and do not survive per-request invocation.
cd computex
gh repo create computex --private --source=. --push
# or: git remote add origin git@github.com:<you>/computex.git && git push -u origin mainRender — render.yaml is a ready blueprint. New → Blueprint, point it at the repo, and it prompts for the secrets marked sync: false.
Railway — railway.json builds from the Dockerfile.
railway init && railway up
railway variables --set PAYEE_ADDRESS=... --set PAYER_MNEMONIC="..."Fly.io — fly.toml is configured; keep min_machines_running = 1.
fly launch --no-deploy
fly secrets set PAYEE_ADDRESS=... PAYER_MNEMONIC="..."
fly deployAny Docker host —
docker build -t computex .
docker run -p 8080:8080 --env-file .env computexSet PAYEE_ADDRESS and PAYER_MNEMONIC as host secrets, never in the repo. PORT is injected by the platform. Everything else has a working default.
/api/pay-and-compute signs USDC with the server's own key, so on a public URL it is an open faucet on your testnet balance. Three guards apply, all tunable:
| Variable | Default | Effect |
|---|---|---|
MAX_JOB_PRICE_USDC |
0.50 |
Refuses any single job quoting more |
RATE_LIMIT_MAX_JOBS |
10 |
Paid jobs per client per window |
RATE_LIMIT_WINDOW_MS |
900000 |
Window length (15 min) |
DEMO_ACCESS_TOKEN |
unset | When set, requires it in the x-computex-token header |
Fund the deployed payer with only what the demo needs — a few USDC is plenty at these prices. These guards limit casual abuse; they are not an authentication boundary. POST /api/compute itself stays open by design: that is the x402 endpoint, and anyone paying it uses their own wallet.
cd computex/server
npm run check:payer # on-chain readiness: balances, USDC opt-in, facilitator support
npm run test:flow # full golden path, ending with an indexer lookup of the tx id
npm run scenarios # recommendation + Agent Mode parsing, no network
npm run simulate # compute simulation and job store, no payment
npm run tx:log # regenerate TRANSACTIONS.md from the chain
npm run test:trace # the wallet path's plumbing: live stage stream + paid jobtest:flow asserts the whole chain: health → 402 → sign → retry → settle → transaction id → confirmed on Algorand Testnet via the indexer. It never trusts the server's word for the transaction id.
TRANSACTIONS.md is the record of every USDC payment this project has settled, with a link to each transaction on the public explorer.
It is generated by npm run tx:log, which reads the payer and payee accounts from the Algorand indexer — not from the server, whose job store is in-memory and resets on restart. The chain is the only durable and independently verifiable ledger here, so the file is regenerated from it rather than accumulated locally. Re-run it after a demo to pick up new payments.
| Method | Path | Notes |
|---|---|---|
GET |
/api/health |
Service + network status |
GET |
/api/catalog |
Workloads, GPUs, priorities, known models |
GET |
/api/providers |
Marketplace supply with live revenue |
GET |
/api/stats |
Dashboard counters |
GET |
/api/jobs · /api/jobs/:id |
Job history |
POST |
/api/quote |
Free price preview |
POST |
/api/recommend |
Deterministic GPU selection |
POST |
/api/agent/parse |
Natural language → requirements → recommendation |
POST |
/api/compute |
x402-protected. 402 until paid |
POST |
/api/pay-and-compute |
Dev payer proxy; NDJSON progress stream |
- Scheme:
exactonalgorand:SGO1GKSzyE7IEPItTxCByw9x8FmnrCDexi9/cOUJOiI=(Testnet), USDC ASA10458941. - Payload: a 2-transaction atomic group — a sponsored fee-payer transaction from the facilitator, plus the client's signed ASA transfer.
- Flow:
authorization— the facilitator verifies before the handler runs and settles after it succeeds. If the job fails, the verified payment is cancelled rather than settled. - Transaction id: only exists after settlement, so it arrives on the
PAYMENT-RESPONSEheader.injectSettlementIntoBodyfolds it into the JSON body and the job record. It is never generated locally.
computex/
├─ .env.example
├─ server/
│ ├─ src/
│ │ ├─ index.ts Hono app, routes, dev payer proxy
│ │ ├─ x402-server.ts x402 wiring, dynamic pricing, settlement capture
│ │ ├─ payer.ts x402 client (holds the key, server-side only)
│ │ ├─ providers.ts GPU marketplace catalog
│ │ ├─ pricing.ts Pay-per-compute pricing model
│ │ ├─ recommend.ts Deterministic GPU scoring
│ │ ├─ agent.ts Natural-language intent parser
│ │ ├─ compute.ts Simulated GPU execution + job store
│ │ ├─ bus.ts In-process stage streaming
│ │ └─ env.ts Configuration
│ └─ scripts/ keygen, optin, check-payer, test-flow, tx-history, dev helpers
└─ client/
└─ src/
├─ App.tsx Console shell + navigation
├─ api.ts API client + NDJSON stream reader
├─ wallet.ts Pera → ClientAvmSigner adapter
├─ walletPay.ts x402 flow in the browser (wallet path)
├─ walletContext.tsx Connected session, shared by header and RunFlow
├─ components/ RunFlow (payment state machine), WalletButton, GPU cards
└─ views/ Marketplace, NewJob, Jobs, Providers, AgentMode
- GPU execution, provider fleets, availability and historical revenue are mock. Everything produced by a job is tagged
simulated: true. - Two payment paths ship. Connect Pera and the browser signs with your own wallet — the server never sees a key, which is how a real product works. With nothing connected it falls back to the shared server-side payer, so the demo still runs for someone who has no testnet account.
- Jobs live in memory and reset when the server restarts.
- Payments, settlement and transaction IDs are real and verifiable on Algorand Testnet.