Quickstart
Start the whole z402 stack with Docker Compose, pay every starter service, run an agent against two providers and list the catalog.
On this page
This page starts the whole z402 stack on one machine, then pays for real: an agent makes shielded deposits on a private Zcash chain and signs a voucher for each call. The chain is private to your machine, so the coins are not real money.
Before you start
Run the stack
Start the stack
Run this in the repository root, the folder that holds
compose.yaml. No.envfile and no other step is needed.Bash docker compose up -d docker compose psup -dbuilds five images, pulls the rest and starts everything in dependency order. The first start downloads about 1 GB of images and two small models (about 580 MB) from Hugging Face. On a 4-CPU VM with a fast link, building the images from nothing took about 1.5 minutes. Once they were built,up -dreturned after 45 to 75 seconds, and every service was healthy within two minutes.Container What it is zebra,zaino,zalletThe private Zcash chain: a node, its indexer, and the agent's wallet. clockMines one block every 25 seconds. Nothing confirms or expires without blocks. facilitatorVerifies vouchers, reads deposits, keeps the ledger and hosts the catalog of paid APIs. apiThe demo paid API. GET /chunkcosts 500 zat.llm,embeddings,dropboxStarter services run by the operator. llmandembeddingsuse local models.markdownThe starter service URL to markdown, run as an independent provider with its own address and API key. llm-model,embed-modelThe llama.cpp servers that hold the two models. webThe website on port 3000. Five one-shot jobs prepare the chain, the wallet, the keys and the registration of provider 2:
chain-init,zallet-init,wallet-fund,keygenandmarkdown-register. They show asExited (0)indocker compose ps -a, and they run again on everyup, changing nothing once their work is done.wallet-fundmines 110 blocks and shields 62.5 ZEC to the agent's wallet.llmandembeddingsshowhealth: startinguntil their model is loaded. Nothing else waits for them.Look at an offer
An unpaid call to the demo API gets the price instead of an answer.
Bash curl -s -o /dev/null -w '%{http_code}\n' http://localhost:4021/chunkText 402The terms travel in the
PAYMENT-REQUIREDheader as base64 JSON. This decodes the offer (it needscurl,base64andjq):Bash curl -s -D - -o /dev/null http://localhost:4021/chunk | tr -d '\r' \ | awk -F': ' 'tolower($1) == "payment-required" { print $2 }' | base64 -d | jq '.accepts[0]'JSON { "scheme": "prepaid-voucher", "network": "zcash:regtest", "amount": "500", "asset": "ZEC", "payTo": "uregtest1...", "maxTimeoutSeconds": 60, "extra": { "windowZat": "100000", "creditRatioBps": 2500, "creditCapZat": "200000", "zeroConfMinExpiryBlocks": 20, "termsVersion": "<16 hex characters>" } }Each call costs 500 zat. The agent deposits
windowZat, 100,000 zat, at a time. How it works explains every field.Pay every starter service
Bash docker compose run --rm smokesmokewaits untilllm,markdown,embeddingsanddropboxare healthy (on a first start, until the models are loaded). It then makes a deposit from the agent's wallet to each provider and runs one short script on every service: anllmstream of up to three chunks, one page, one embedding, and a drop uploaded, held, fetched and deleted. It checks every answer, prints one line per step, and exits 0 if all passed.Text llm start 200 31842 ms chunk 0 "Once upon a time, there lived a lighthouse keeper named Elias." (200 zat) llm continue 200 1910 ms chunk 1 " Every night he climbed the stairs to light the lamp" (200 zat) llm continue 200 1895 ms chunk 2 "..." (done, length) (200 zat) markdown get 200 412 ms "Example Domain" 149 chars (1000 zat) embeddings embed 200 96 ms 2 vectors, 768 dimensions, model nomic-embed-text, 5 tokens (100 zat) dropbox upload 201 40 ms id q3Zs0mYbT7cWpAxK1nGhUw, 1192 bytes, held until block 1052 (500 zat) dropbox hold 200 75 ms held until block 1062 (500 zat) dropbox fetch 200 5 ms 1192 bytes identical, expires at block 1062 dropbox delete 204 4 ms deleted, then 404 session uregtest1qqq…qqqqqq cumulative 1700 zat session uregtest1ppp…pppppp cumulative 1000 zat 7 paid calls over 2 sessions smoke ok: 9 steps passed on llm, markdown, embeddings, dropboxYour times, texts and ids will differ. Each line reads
service step status time detail. The first line is slow because it includes the first deposit: the wallet has to build a proof, which takes 5 to 10 seconds. The twosessionlines are the two providers: the operator, which runsllm,embeddingsanddropbox, and provider 2, which runsmarkdown. Each has its own deposit and its own running total. A failed step saysFAILand whether the call was charged.markdownfetcheshttps://example.com, so themarkdowncontainer needs internet access. To fetch another page, add--page:Bash docker compose run --rm smoke --page https://www.rfc-editor.org/rfc/rfc9110.htmlRun an agent against two providers
The demo agent is the stock x402 client plus the z402 scheme. This run calls the demo API and URL to markdown in turn, five times each:
Bash docker compose run --rm agent --calls 5 \ --url http://api:4021/chunk \ --url "http://markdown:4032/v1/markdown?url=https://example.com"Inside the compose network the services have their container names and ports (
api,markdown). From your own machine, the same services are onlocalhostand the published ports.Text wallet: 6,249,460,000 zat spendable z402 demo: 5 calls to each of 2 targets, in turn, every 1000 ms on zcash:regtest [1] http://api:4021/chunk [2] http://markdown:4032/v1/markdown?url=https://example.com * session 7c1e…9d on zcash:regtest * deposit D1 started, paid inline * deposit D1 broadcast 3f2a…c1 in 6,888 ms * deposit 3f2a…c1 acknowledged by the merchant #001 [1] 200 c=500 limit=100,000 conf=0 pend=100,000 credit=0 D1 3f2a…c1 pending * session 2b90…e4 on zcash:regtest * deposit D1 started, paid inline * deposit D1 broadcast 91d0…7a in 6,214 ms * deposit 91d0…7a acknowledged by the merchant #002 [2] 200 c=1,000 limit=100,000 conf=0 pend=100,000 credit=0 D1 91d0…7a pending #003 [1] 200 c=1,000 limit=100,000 conf=0 pend=100,000 credit=0 D1 3f2a…c1 pending #004 [2] 200 c=2,000 limit=100,000 conf=0 pend=100,000 credit=0 D1 91d0…7a pending ... per target: [1] 5 paid c=2,500 session 7c1e…9d payTo uregtest1qqq…qqqqqq deposits 1 [2] 5 paid c=5,000 session 2b90…e4 payTo uregtest1ppp…pppppp deposits 1 doneHow to read it:
[1]and[2]tag the two targets. They have different payTo addresses, so the agent opens one session, with its own session key and its own deposit, for each. Each provider sees only its own session.c=is the running total the agent has signed for with that provider. It grows by the price of each call: 500 zat at the demo API, 1,000 zat at URL to markdown.limit=is the most that provider will accept right now.conf=,pend=andcredit=are its three parts: confirmed deposits, one unconfirmed deposit, and credit.D1 3f2a…c1 pendingis the deposit and its status. Call 1 is served while the deposit is still in the mempool. Once a block confirms it, the status becomesconfirmed, and the limit grows by the 25 percent credit.
The same agent has more modes:
docker compose run --rm agent --calls 20docker compose run --rm agent --calls 300 --interval-ms 0 --cheatThe cheat run makes one deposit and refuses to make another. About 200 calls run on the unconfirmed deposit, the agent waits for a block, about 50 more run on credit, and the provider then refuses at its limit of 125,000 zat with
z402_deposit_required. That is the most a cheating agent can ever get: one unconfirmed window plus the credit line.List the services
Services list themselves in the facilitator's catalog, the x402 Bazaar, and repeat it every 30 seconds.
discoverreads the catalog the way any agent would, with no wallet:Bash docker compose run --rm discoverText Demo API GET http://localhost:4021/chunk 500 zat per chunk up, 5 paid today LLM inference POST http://localhost:4031/v1/generate 200 zat per output chunk up, 3 paid today URL to markdown GET http://localhost:4032/v1/markdown 1,000 zat per page up, 6 paid today Text embeddings POST http://localhost:4033/v1/embeddings 100 zat per 1k tokens up, 1 paid today Encrypted drop box POST http://localhost:4034/v1/drops 500 zat per 10 blocks up, 1 paid today Encrypted drop box POST http://localhost:4034/v1/drops/:id/hold 500 zat per 10 blocks up, 1 paid todayEach line ends with the route's description, left out here.
upmeans the service declared itself in the last 90 seconds.paid todaycounts calls settled since 00:00 UTC. After the two runs above,llmshows the three chunks of the smoke run, andmarkdownshows its one smoke page plus the five the agent fetched. The demo API'sGET /chunkis listed too, asDemo API, and shows the five calls the agent paid it. The URLs are the services' public addresses: reach them from your machine, not from inside the compose network.--intentranks the list on your side by word overlap, so the catalog never learns what you are looking for:Bash docker compose run --rm discover --intent "turn a web page into markdown"The website shows the same list at
http:. These docs are served by the same site, at/ / localhost: 3000/ services /docs.Stop or reset
Bash docker compose stop # stop the containers, keep everything docker compose start docker compose down -v # delete every volume
Ports and settings
| Service | Port | Setting |
|---|---|---|
web | 3000 | Z402_WEB_PORT |
api | 4021 | Z402_API_PORT |
llm, markdown, embeddings, dropbox | 4031 to 4034 | Z402_LLM_PORT, Z402_MARKDOWN_PORT, Z402_EMBEDDINGS_PORT, Z402_DROPBOX_PORT |
facilitator | 4022 | not published. The website proxies its public catalog reads. |
Copy .env.example to .env to change a port, the block time (Z402_BLOCK_SECONDS) or the models. Published ports bind to every interface by default, so a server serves them. On a laptop, set Z402_PUBLISH_HOST= so only your machine reaches them. Docker's published ports bypass ufw.
Next steps
References
docs/deploy.md: prerequisites, first start, ports, the agent and how to stop and wipe.compose.yaml: the services, the one-shot jobs and the profiles ofagent,discoverandsmoke..env.example: the compose settings.apps/smoke/README.md: whatsmokeruns and how its output reads.apps/agent/src/flags.ts:--url,--calls,--interval-msand--cheat.apps/agent/src/log.ts: the format of the agent's log lines.apps/agent/src/discover.ts: thediscovercommand and its list format.docs/demo.md: the cheat run and the numbers behind it.docs/research/live-run.md: deposit timings measured on the regtest stack.services/README.md: the starter services and how they list themselves.