Inspiration

An AI agent can call an API. It cannot buy one.

Every payment rail we have was designed with a human in the loop. Sign up, confirm an email, generate an API key, put a card on file, wait for an invoice. An agent that discovers a useful tool at runtime — three seconds into a task, in the middle of a reasoning loop — can do none of that. It can only use what somebody provisioned for it in advance, which means the set of things an agent can do is fixed before it ever starts thinking.

And even if you could automate the signup, the economics don't work. Card processing has a floor of roughly 30¢ per transaction. A single language-detection call is worth about a fifth of a cent. There is no way to charge for it, so nobody builds it, so it doesn't exist.

Then we found x402 — the revival of HTTP's long-dormant 402 Payment Required status code — and the shape of the answer fell out immediately. If a server can answer "pay me first" in a way a machine understands, and the payment is a stablecoin transfer that settles in under a second for a fraction of a cent, then an agent doesn't need an account. It needs a wallet. The tool it found thirty seconds ago becomes a tool it can use right now.

So we built the marketplace that assumes that world already exists.

What it does

tradex is an open marketplace where AI agents buy tools from each other, per call, in USDC on Solana. No accounts. No API keys. No invoices.

A provider lists an HTTP endpoint. A buyer — a human clicking a button, or an autonomous agent mid-task — calls it, gets a 402 back with machine-readable payment requirements, signs an SPL transfer, retries, and gets their result. The whole handshake takes one round trip and costs a fraction of a cent.

There are thirteen live, paid services on the shelf, and every one of them is real — a Gemini-backed endpoint behind a real payment gate, not a mock row in a table. Research with Google Search grounding ($0.02). Scrape-and-summarize with structured extraction ($0.01). SQL generation, entity extraction, content moderation, code explanation, regex building, translation. The cheapest is a language detector at $0.002 — a price that simply cannot exist on any card rail on earth.

Three things make it more than a directory:

The buyer agent. Give it a task in plain English and watch it work: it searches the marketplace, reads a listing's input schema, pays for the one that fits, calls it, and composes an answer. It's a Gemini function-calling loop with exactly three tools — search_marketplace, get_listing, call_paid_agent — and a hard cap of five turns. The third tool is where money moves. Every step streams to the browser over SSE, so you watch it discover, pay, call, and answer, with the Solana transaction signature appearing in real time.

It's an MCP server. This is the part we're proudest of. Mount tradex in Claude Desktop, Claude Code, or Cursor, and your agent gets a Solana wallet and a marketplace it can spend from. The tool surface is five tools, and one of them — buy — spends real money. On first run the server generates a wallet for you, writes it to ~/.tradex/wallet.json with mode 0600, and tells you how to fund it. Browsing is free; buying is capped at $0.10 per call and $1.00 lifetime until you say otherwise. The marketplace never holds your funds and never sees your key.

Reputation is not a number we store. It's the provider wallet's on-chain settlement history — read back from any Solana RPC with getSignaturesForAddress — plus a rating tally living in a native Rust program we wrote and deploy. Anyone can recompute a provider's entire track record without asking us, or trusting us. We are not the ledger. The chain is.

How we built it

The stack is Next.js 16 (App Router), x402 v2, Solana, Postgres, Gemini, and 183 lines of Rust.

The payment rail. Providers wrap their handler in a gated() helper that turns an unpaid request into a 402 carrying x402 PaymentRequirements. Buyers go through a single function, payAndCall, which performs the full round trip: call, receive the 402, sign an SPL transferChecked, retry with an X-PAYMENT header, and read the settlement signature back out. That function is deliberately the only door to the network — the website, the buyer agent, Claude Code's MCP server, and the CLI test scripts all reach the chain through it, which is why one bookkeeping write in one place captures every purchase the system has ever made.

We run our own facilitator. In local mode, the x402 facilitator runs in-process using the official @x402/svm facilitator scheme, and is exposed over the standard REST surface at /api/facilitator/{supported,verify,settle} — so it is a genuine, callable facilitator, not a stub. Crucially, it is the fee payer. That means a buyer agent needs USDC and nothing else: it never has to hold SOL, never has to top up gas, never has to know Solana exists beyond "I have a wallet."

The Rust program. A native Solana program (no Anchor) storing one reputation record per provider at a PDA derived from ["rep", provider_pubkey]. Four u64s — call count, total rating points, rating count, total volume — in a fixed-size account that's allocated once and never resized. One instruction, RecordRating. The PDA derivation is the authorization check: you cannot point the program at some other account and have it credit the wrong provider.

Gemini does four different jobs. Structured output with responseJsonSchema for every extraction service. Google Search grounding with citations for the research agent. Embeddings for semantic marketplace discovery. And function calling for the buyer agent's tool loop. Every response reports which engine actually served it — a detail that turns out to matter enormously (see below).

On the chain vs. the database. The chain knows a payment happened; it does not know what was bought. The database knows what was bought; it can be wrong. So the history view reads both and joins them on the transaction signature. If we have a ledger row, you see the tool, the input, and the result. If we don't — because some third-party client that never touched our code made the payment — we still show the transfer, marked as unattributed. We'd rather show a payment we can't explain than hide it.

Challenges we ran into

The devnet faucet doesn't work. This sounds trivial. It ate a day. Solana's public devnet faucet is rate-limited to the point of being unusable for a hands-off demo — it just returns 429. So we made the whole thing run against a local solana-test-validator with our own SPL mint: same runtime, same programs, real signatures, real Explorer links, but a faucet that actually dispenses. The trick was making that a mode rather than a fork. priceFor() returns an explicit AssetAmount against our mint locally, or a plain USD Money string in hosted mode, where the SVM scheme resolves it to Circle's real devnet USDC. Flip one env var and the identical code path settles real Circle USDC through the public x402.org facilitator. Nothing else changes.

The 402 body is legitimately empty. We lost hours convinced the SDK was broken. In x402 v2, PaymentRequirements travel in a base64-encoded PAYMENT-REQUIRED header, not the response body — so a correct 402 has a body of {}. It looks exactly like a bug and isn't.

The client built transactions against the wrong cluster. The convenience registration helper derives an RPC endpoint from the CAIP-2 network identifier, which is fine until you're on a local validator, at which point it silently signs transactions for a chain you aren't on. The fix was to stop using the helper and register the ExactSvmClientScheme class directly with an explicit rpcUrl.

A memoization bug that would have charged the wrong price. Our nine "skill" services share one dynamic route, /api/agents/skill/[slug]. The gated() wrapper memoizes itself on first call, baking in that call's price. Wrapping the route once at module scope meant whichever skill was requested first would freeze its price and charge it for all nine — a $0.002 language detection billed at $0.012 forever after. We key the gate cache by slug now. It was a two-line fix for a bug that would have quietly stolen from every user.

Serverless silently voids any invariant you keep in memory. The MCP spend cap started as an integer in a closure. That's a real budget in a long-lived stdio process. In a serverless HTTP handler, the module is rebuilt on every single request, so the counter resets to zero every call and the cap enforces nothing. Same code, same logic, completely decorative. The remote budget now lives in Postgres. The same class of bug bit us again with call logging: recordCall was fire-and-forget, and rows kept vanishing — because a serverless function tears down pending promises the moment it responds, and a short-lived CLI process exits before the insert flushes. Real purchases were showing up as unattributed. It's awaited now, wrapped so it can never fail a call the user has already paid for.

Gemini's free tier dies at the worst moment. Free-tier quota is metered per model, per day, and once a model is spent it returns 429 until midnight Pacific — which is precisely the kind of thing that kills a live demo in front of judges. So generate() walks a ladder of models and steps down it on quota errors instead of failing. But the important half is the honesty: every response reports the model that actually served it, and if no key is configured at all, the system says so out loud and runs a deterministic planner instead. A fallback result is never, ever passed off as a Gemini one.

Telling an agent it's broke. When buy was called on an empty wallet, the failure surfaced as an opaque transaction-simulation error from the facilitator — and the model, reasonably, would just try again. And again. Now there's a balance pre-flight that returns plain English: here is your address, here is where to get devnet USDC, nothing was spent. Designing error messages for a reader who will act on them, rather than a human who will squint at them, turned out to be a genuinely different discipline.

Accomplishments that we're proud of

It actually settles. npm run e2e makes an unpaid call and asserts a 402, pays, asserts a 200, and then reads the buyer's and provider's token balances off the chain to prove the money moved. Not a mocked payment. Not a simulated one. A real SPL transfer with a signature you can click.

The MCP tool that spends money. We think buy might be one of the first MCP tools whose side effect is an on-chain payment, and getting its ergonomics right was a genuinely novel design problem. It's annotated readOnlyHint: false, idempotentHint: false so hosts prompt the human before running it. Two hard caps sit in front of it and it refuses rather than overspending. And when a service fails, it says — explicitly, in the error text — you were not charged, because settlement only fires on a successful response. So the agent doesn't believe it wasted money it still has.

A failing service can never charge you. withX402 settles only after the handler returns a status below 400. Malformed input is a 400. An upstream failure is a 502. Neither one moves a cent. That guarantee is load-bearing for the entire trust model of an anonymous marketplace, and it's enforced by the protocol rather than by our good intentions.

Zero-config for a stranger. Point any MCP client at the server with no environment variables at all, and it generates you a wallet, defaults to the public devnet marketplace, lets you browse for free, and refuses to buy until you fund it — telling you exactly how. That's the real path. Everything else is a demo.

What we learned

Payment changes the shape of a tool. We had built plenty of agent tools before. None of them had to answer "what happens if you call this twice?" with anything more serious than a shrug. When the answer costs money, idempotency stops being a nicety, refusal becomes a first-class return value, and the difference between failed and failed but charged you becomes the most important thing in the error message. Half our design effort went into the failure paths.

Reputation is better as a derivation than a column. The instinct is a reviews table. But if we store it, you have to trust us — and a marketplace that asks for trust it hasn't earned is just a middleman. Deriving it from settlement history means anyone can recompute a provider's record from any RPC in the world, and it survives us shutting the site down. It also means we cannot cheat, which is a much stronger promise than saying we won't.

Two independent sources beat one authoritative one. We read reputation from both the transfer history and the program's PDA, and take the larger count. They should agree — but a validator that has pruned its signature index shouldn't be able to erase an honest provider's track record just because the RPC forgot.

Sub-cent pricing isn't a smaller version of normal pricing. It's a different market. Once a call can cost $0.002, "should I buy this?" stops being a procurement decision and becomes a runtime one. That single change is what makes an autonomous buying agent coherent instead of terrifying.

What's next for TradeX

Real ratings. Right now a successful settlement writes a 5 to the on-chain program. That's a placeholder and we'd rather say so than dress it up. The next version has the buyer rate what it actually got, so the on-chain average means something.

Mainnet, and a real provider onboarding flow. Everything here runs on throwaway devnet keys by design. Going to mainnet means provider identity, listing verification, and a much harder look at what a malicious listing could do to a buying agent.

Escrow and streaming payments. exact works beautifully for a single sub-cent call. It doesn't work for a job that takes four minutes and costs a dollar. That wants an escrow, or a payment that streams as the work completes.

Spend policies as a first-class object. Two flat caps is a starting point, not an answer. Real delegation looks like per-agent budgets, provider allowlists, category limits, and rate ceilings — a policy the human writes and the agent cannot talk its way around.

Agents that resell. The moment a buying agent can also list, you get composition: an agent that buys three cheap services, adds judgment, and sells the result for more than it paid. That's not a marketplace of tools anymore. That's an economy — and it's the reason we built the payment rail before we built anything else.

Built With

  • blinks
  • gemini-api
  • gemini-embeddings
  • google-gemini
  • mcp
  • model-context-protocol
  • next.js
  • node.js
  • postgresql
  • react
  • rust
  • solana
  • solana-actions
  • solana-kit
  • solana-program
  • solana-wallet-adapter
  • solana-web3.js
  • spl-token
  • swr
  • tailwindcss
  • typescript
  • usdc
  • x402
  • zod
Share this project:

Updates

Submission history