# What is x402 Work? x402 Work is a way to pay for an HTTP request with a few seconds of computation instead of money from a wallet. ## Overview [x402](https://www.x402.org) revives HTTP status 402, Payment Required. A server answers a request with a price, the client pays, and retries the same request with proof of payment. The standard x402 scheme, `exact`, pays by transferring a stablecoin from a wallet. x402 Work adds a scheme called `work`. The price is a small proof-of-work puzzle, the payment is its answer, and the server checks the answer in a single step. The headers, the envelope and the retry are the same as any x402 payment. The scheme isn’t tied to one chain. A profile names the proof-of-work algorithm and the network a job comes from, so any proof-of-work chain can price requests: SHA-256d, Scrypt, KawPoW, kHeavyHash, RandomX and others. Quai Network’s SHA-256d, Scrypt and KawPoW profiles and Monero’s RandomX profile are defined today; this site hosts `quai/sha256d-v1`, `quai/scrypt-v1`, `quai/kawpow-v1` on `quai:9`; `monero/randomx-v1` on `monero:418015bb9ae982a1975da7d79277c270`. x402 Work is built by [Dominant Strategies](https://dominantstrategies.io), the team behind Quai Network, and proposed upstream to x402: [the `work` scheme and its profiles](https://github.com/dominant-strategies/x402/pull/3), alongside [`exact` payments in QUAI and Qi](https://github.com/dominant-strategies/x402/pull/2). Both are documented here: [the work scheme](https://x402.qu.ai/docs/scheme-work) and [exact payments on Quai](https://x402.qu.ai/docs/scheme-exact-quai), with [facilitators](https://x402.qu.ai/docs/facilitators) and [libraries](https://x402.qu.ai/docs/libraries) to integrate them. ## x402 Work compared with x402 | x402 (exact) | x402 Work What pays | A stablecoin transfer, such as USDC | A proof of work: a few seconds of hashing What the client needs | A funded wallet and its private key | A CPU or GPU, and nothing else What a payment reveals | The payer’s address, on a public ledger | Nothing about who paid Money up front | Yes, before the first request | None How it settles | A facilitator broadcasts the transfer | The server verifies the proof and records it, so it is spent once What the seller receives | The stablecoin | The work, which can win a block reward on its network Which networks | Chains with a supported stablecoin | Any proof-of-work algorithm, through profiles (Quai’s are live) Headers | PAYMENT-REQUIRED, PAYMENT-SIGNATURE, PAYMENT-RESPONSE | The same ## Why pay with work Benefit | What it means More private | There is no wallet address, account or card behind a payment, so a request can’t be tied to a person by how it was paid for. No wallet needed | Anyone with a device can pay: a person in a browser, an agent on a server, a script on a laptop. There are no funds to top up first. Made for agents | An agent can buy what it needs, one request at a time, inside a work limit you set. No sign-ups, keys or checkout pages. Fair to sellers | Bots that hammer a site for free now pay for each request, and the work goes to the seller instead of being lost to a captcha. ## Why the work is worth something The puzzles are real mining jobs from a proof-of-work network, made much smaller; with today’s profiles, Quai Network jobs or Monero pool jobs. Most answers only prove the work was done; occasionally one is good enough to count on the network, which earns a block reward for the seller’s address. That shared value is what makes a few seconds of computation a payment rather than a speed bump. A work receipt proves an access payment. It is not a promise of a mining reward, and it is not a balance or credit. --- # How it works Two ordinary HTTP requests with a small puzzle in between. ## The exchange 1. Request: The client sends the request normally, with nothing attached. 2. 402 with a job: The server answers `402 Payment Required`. The `PAYMENT-REQUIRED` header (base64 JSON, also in the body) describes the resource and offers one or more work jobs, each with a price. 3. Solve: The client looks for a nonce that makes the job’s hash fall below the target. There is no shortcut, so it guesses until one fits. 4. Retry with the answer: The client sends the identical request again with `PAYMENT-SIGNATURE`: base64 JSON of `{x402Version, resource, accepted, payload: {solution}}`. 5. 200 with a receipt: The server verifies the answer, records it so it can’t be spent twice, serves the response and attaches `PAYMENT-RESPONSE`. ## The challenge PAYMENT-REQUIRED (decoded) ``` {"x402Version":2, "resource":{"url":"https://x402.qu.ai/x402/v1/demo/image","mimeType":"image/gif"}, "accepts":[{"scheme":"work","network":"quai:9","asset":"hash", "amount":"65536","payTo":"0x00…","maxTimeoutSeconds":15, "extra":{"name":"x402-work","version":"1","profile":"quai/sha256d-v1", "job":{"id":"…","expiresAt":1760000015,"refreshAfter":1760000004,"work":{…}}}}]} ``` Field | Meaning amount | The price, in expected guesses. A valid answer scores at or below `floor(2^256 ÷ amount) − 1`. payTo | The seller’s address: QUAI for Quai profiles, Monero for `monero/randomx-v1`. Jobs are built for it, so the work can only benefit that address. maxTimeoutSeconds | The most seconds between the 402 and the paid retry. The job’s `expiresAt` is never later than the time of the offer plus this. Offers from this site use `15`, and `60` for RandomX. extra.profile | Which algorithm the job uses. extra.job | The mining job and its expiry, in Unix seconds. Stop at `refreshAfter` or `expiresAt`, whichever comes first, and ask for a new job. ## Work profiles Profile | Algorithm | x402-work-client can pay it | Solution quai/sha256d-v1 | SHA-256d | Yes, in the least CPU time | `{"extraNonce2","time","nonce"}` quai/scrypt-v1 | Scrypt (N=1024, r=1, p=1) | Yes, slower on a CPU | `{"extraNonce2","time","nonce"}` quai/kawpow-v1 | KawPoW | No: needs a GPU miner with the KawPoW DAG | `{"nonce","mixHash"}` monero/randomx-v1 | RandomX (Monero’s `rx/0`) | Yes, with the `x402-work-randomx` solver: work a CPU does well | `{"nonce"}` This site hosts `quai/sha256d-v1`, `quai/scrypt-v1`, `quai/kawpow-v1` on `quai:9`; `monero/randomx-v1` on `monero:418015bb9ae982a1975da7d79277c270`. [Pay with work](https://x402.qu.ai/docs/pay/profiles) explains how to choose a profile you can pay. ## The receipt `PAYMENT-RESPONSE` is base64 JSON: `{success, transaction, network, amount, extra}`. `transaction` is a fingerprint of the proof, not a chain transaction, and `amount` counts expected guesses, not coins. `extra.sourceAccepted` records an upstream acknowledgement of a submitted proof. It does not prove an on-chain reward. ## Rules both sides follow Rule | Why Same request twice | The retry must match the first request exactly. A different body is a different price and gets a fresh 402. Spent once | A proof pays for one request. Replaying it gets a fresh 402 with `invalid_work_state`; marketplace tools answer `409` instead, because the purchase already settled and its result can be recovered. Consent | Clients start work only when a person or policy allows it, inside a time and guess limit. Check before solving | Clients check the resource URL, network, profile, price and expiry before spending anything. --- # Pay with work Pay for any request that offers x402 work, from Node, an agent, a backend or a script. No account or wallet. ## Install the client `x402-work-client` sends the request, checks the 402 offer against your price limit, solves a SHA-256d or Scrypt puzzle on your machine (or RandomX, with [the RandomX solver](https://x402.qu.ai/docs/pay/randomx)) and retries. It has no dependencies and needs Node 20 or later. Install it into your project as a library; the `x402-work` command comes with it. No Node? The exchange is two HTTP requests and a hash loop: [pay from Python](https://x402.qu.ai/docs/pay/raw) with the standard library. Install ``` npm install https://x402.qu.ai/x402-work-client-0.2.0.tgz ``` JavaScript ``` import { payWithWork } from "x402-work-client"; // A GET; pass an object as the second argument to POST it as JSON. const { response, payment } = await payWithWork("https://x402.qu.ai/x402/v1/demo/image", undefined, { maxPrice: 500_000, // the most work you accept, in expected guesses }); console.log(response.status, payment?.profile, payment?.guesses, payment?.receipt?.success); ``` Shell ``` # In the project where you installed it: npx x402-work pay https://x402.qu.ai/x402/v1/demo/image -o image.gif npx x402-work pay https://hashference.com/x402/v1/chat/completions \ --data '{"model":"","messages":[{"role":"user","content":"Hi"}],"max_tokens":64}' # Or without installing anything: npx --yes --package https://x402.qu.ai/x402-work-client-0.2.0.tgz x402-work pay https://x402.qu.ai/x402/v1/demo/image -o image.gif ``` An offer above `maxPrice` is refused before any work starts. Work is spent only on a request you chose to make. The tarball’s sha256 is `92586052e94f225a9a80ee68d8faec08b5d9903392466301219c3ee0787fc4ab`. ## Choose a profile you can pay A 402 can offer several jobs in `accepts`, one per profile. Pick the first one whose profile you can solve, and decline the request if there is none. `payWithWork` does this for you and prefers SHA-256d, which takes a CPU the least time; pass `profiles` to change the order. Profile | Who can pay it | Notes quai/sha256d-v1 | x402-work-client in Node, or any language with SHA-256 | A few hundred thousand guesses a second in Node on one core, about a million in Python with `hashlib`, and about 50,000 in a browser. quai/scrypt-v1 | x402-work-client, in Node | Scrypt with N=1024, r=1, p=1 and a 32-byte output, the same as Litecoin. About a hundred times slower than SHA-256d on a CPU: a few thousand guesses a second in Node. Hosted jobs refresh after 4 seconds, so a CPU pays Scrypt prices up to about 4,096 reliably; ASIC miners are far faster. quai/kawpow-v1 | An external GPU miner | Needs the KawPoW DAG for the job’s height, so there is no CPU solver. Hand the job to a miner through a local Stratum adapter and send back its `nonce` and `mixHash`. monero/randomx-v1 | `x402-work-randomx`, from Node or any language | Monero’s CPU algorithm. About 80 guesses a second per core, or about 1,000 with `-fast` and 2 GiB of memory. Jobs last 60 seconds. [Pay with RandomX](https://x402.qu.ai/docs/pay/randomx). This site hosts `quai/sha256d-v1`, `quai/scrypt-v1`, `quai/kawpow-v1` on `quai:9`; `monero/randomx-v1` on `monero:418015bb9ae982a1975da7d79277c270`. Sellers choose which profiles to offer; a seller that offers only KawPoW can’t be paid by the reference client. A guess is worth different amounts on different algorithms. SHA-256d is mined with ASICs millions of times faster than a CPU, so a CPU’s SHA-256d guesses are worth very little. A seller that accepts them cheaply is paying for most of the request itself, and may raise the price or stop offering it. ## Pay with RandomX RandomX is Monero’s proof of work, built so that CPUs do it well. A `monero/randomx-v1` job is a Monero pool job for the seller’s Monero address: a 4-byte nonce in a hashing blob, keyed by the pool’s seed hash. This site hosts it. Hashing it needs the reference RandomX library, so this site serves `x402-work-randomx`, a solver built from it: [x402-work-randomx-linux-amd64](https://x402.qu.ai/x402-work-randomx-linux-amd64) (sha256 `9264c0966df63e588cf386fe80c40fc290a20baf52f373a360fe5bca9468fe70`). It is statically linked for Linux on x86-64; check its sha256, then make it executable. Node ``` import { payWithWork } from "x402-work-client"; // Prefers RandomX when it is offered, and falls back to SHA-256d. const { response, payment } = await payWithWork(url, body, { randomx: { solver: "./x402-work-randomx-linux-amd64" }, // or set X402_WORK_RANDOMX_SOLVER maxPrice: { "monero/randomx-v1": 50_000 }, // the most RandomX guesses you accept }); ``` Shell ``` curl -fO https://x402.qu.ai/x402-work-randomx-linux-amd64 && chmod +x x402-work-randomx-linux-amd64 npx x402-work pay --randomx-solver ./x402-work-randomx-linux-amd64 ``` Any language ``` # One JSON request per line on stdin, one answer per line on stdout. # target is floor(2^256 / amount) - 1 as 0x + 64 hex digits; start is a random nonce. echo '{"id":1,"blob":"","seedHash":"","target":"0x…","maxHashes":24576,"start":123456}' \ | ./x402-work-randomx-linux-amd64 {"id":1,"nonce":"0x1c0e0b2f","hash":"0x0000…","result":"…","hashes":1834} # Send {"nonce": "0x1c0e0b2f"} as the solution. ``` Option | What it does Light mode (default) | Builds a 256 MiB cache once per Monero seed, in about a second. About 80 guesses a second per core. `-fast` | Also builds the 2 GiB dataset, in a few seconds with every core. About 1,000 guesses a second per core: worth it for an agent that pays often. Keep the solver running between payments (`createRandomxSolver` in Node) so it builds them once. `-threads N` | Hashing threads; the default is every core. Other platforms | Build it from source with Go 1.24 and a C compiler: `go build ./cmd/x402-work-randomx` in `native-verifier` of the [`x402-work-provider` package](https://x402.qu.ai/docs/libraries). RandomX 2.0.1 is vendored there unmodified. The solver is a native program: it runs with your user’s permissions. Download it only from this site, and check its sha256 against [x402-packages.json](https://x402.qu.ai/x402-packages.json). ## Where the code runs You are building | Pattern An agent or script | Runs the exchange itself, with its own CPU. A website | Calls a route on your backend, which runs the exchange and returns the result. Or, for small prices, solve in the visitor’s browser with a Web Worker, as the [demo](https://x402.qu.ai/demo) does. A native app | Runs the exchange on the device. ## The exchange without a library No library is needed: two HTTP requests and a solver for the profile you accept. The Python version runs with the standard library alone and includes a SHA-256d solver that stops when the job expires. JavaScript ``` // Pay for one request with work. Any 402 that offers the "work" scheme. const url = "https://x402.qu.ai/x402/v1/demo/image"; const b64 = value => Buffer.from(JSON.stringify(value)).toString("base64"); const decode = value => JSON.parse(Buffer.from(value, "base64").toString()); const first = await fetch(url); if (first.status !== 402) throw new Error("Expected a 402"); const required = decode(first.headers.get("payment-required")); const accepted = required.accepts.find(a => a.scheme === "work" && a.network === "quai:9" && a.extra?.profile === "quai/sha256d-v1"); // Check the resource, price and expiry before spending anything. if (!accepted || required.resource.url !== url || BigInt(accepted.amount) > 1_000_000n) throw new Error("Offer refused"); const solution = await solve(accepted); // your solver; see "Solve the job" and "Worked example" const paid = await fetch(url, { headers: { "payment-signature": b64({ x402Version: 2, resource: required.resource, accepted, payload: { solution } }), } }); const receipt = decode(paid.headers.get("payment-response")); console.log(paid.status, receipt.success, receipt.amount); ``` Python ``` # Pay for one request with work, using only the Python standard library. import base64, hashlib, json, os, struct, time, urllib.error, urllib.request URL = "https://x402.qu.ai/x402/v1/demo/image" BODY = None # or json.dumps({...}).encode() to POST JSON; send the same bytes both times MAX_PRICE = 1_000_000 # the most work you accept, in expected guesses def b64(value): return base64.b64encode(json.dumps(value).encode()).decode() def decode(value): return json.loads(base64.b64decode(value)) def sha256d(data): return hashlib.sha256(hashlib.sha256(data).digest()).digest() def fetch(headers={}): if BODY is not None: headers = {**headers, "content-type": "application/json"} try: response = urllib.request.urlopen(urllib.request.Request(URL, BODY, headers)) except urllib.error.HTTPError as error: response = error return response.status, response.headers, response.read() def solve(accepted): job, amount = accepted["extra"]["job"], int(accepted["amount"]) work, target = job["work"], (1 << 256) // amount - 1 deadline = min(job.get("refreshAfter", job["expiresAt"]), job["expiresAt"]) extra_nonce2 = os.urandom(work["extraNonce2Size"]) coinbase = bytes.fromhex(work["coinbase1"] + work["extraNonce1"]) + extra_nonce2 + bytes.fromhex(work["coinbase2"]) merkle = sha256d(coinbase) for branch in work["merkleBranches"]: merkle = sha256d(merkle + bytes.fromhex(branch)) previous = bytes.fromhex(work["previousBlockHash"]) previous = b"".join(previous[i:i + 4][::-1] for i in range(0, 32, 4)) # reverse each 4-byte word header = struct.pack("= deadline: raise TimeoutError("job expired: request a new one") inner = first.copy() inner.update(struct.pack(" MAX_PRICE: raise SystemExit("Offer refused") solution = solve(accepted) signature = b64({"x402Version": 2, "resource": required["resource"], "accepted": accepted, "payload": {"solution": solution}}) status, headers, body = fetch({"payment-signature": signature}) receipt = decode(headers["payment-response"]) print(status, receipt["success"], receipt["amount"], len(body), "bytes") ``` Keep the request identical on both calls. If it changes, so does the price, and you get a fresh 402. ## Read the challenge The 402 carries `PAYMENT-REQUIRED`: base64 JSON, also in the body. Decoded ``` {"x402Version":2, "resource":{"url":"https://x402.qu.ai/x402/v1/demo/image","mimeType":"image/gif"}, "accepts":[{"scheme":"work","network":"quai:9","asset":"hash", "amount":"65536","payTo":"0x00…","maxTimeoutSeconds":15, "extra":{"name":"x402-work","version":"1","profile":"quai/sha256d-v1", "job":{"id":"…","expiresAt":1760000015,"refreshAfter":1760000004,"work":{…}}}}]} ``` Field | Meaning amount | The price in expected guesses. A valid answer scores at or below `floor(2^256 ÷ amount) − 1`. payTo | The seller’s address: QUAI, or Monero for `monero/randomx-v1`. Check it against the address you expect before solving. maxTimeoutSeconds | The most seconds between this 402 and your paid retry: `expiresAt` is never later than the offer’s time plus this. `15` on this site, `60` for RandomX. extra.profile | The algorithm. Choose the first offer whose profile you can solve. extra.job.work | The job. For SHA-256d and Scrypt it is a Stratum job: coinbase parts, merkle branches, version, bits, time and `extraNonce1`, as lowercase hex without `0x`. For KawPoW it is a header hash, seed hash, height and bits. For RandomX it is a Monero hashing `blob`, the `seedHash` that keys RandomX, and the `height`. extra.job.expiresAt, refreshAfter | Unix seconds. Stop at the earlier of the two and ask for a new job. ## Solve the job For `quai/sha256d-v1` and `quai/scrypt-v1`, build an 80-byte block header from the job and your solution, hash it, and keep trying nonces until the score is at or below the target. Byte order matters at every step: Field | Encoding in the job or solution | In the header extraNonce2 | Solution: `"0x"` + `extraNonce2Size` bytes (4) of your choice, lowercase hex. | Copied into the coinbase byte for byte, as written. Coinbase | `coinbase1 ‖ extraNonce1 ‖ extraNonce2 ‖ coinbase2`, each hex string decoded to bytes as written. | Not part of the header itself. Merkle root | `m = SHA256d(coinbase)`, then for each branch in order `m = SHA256d(m ‖ branch)`, branch bytes as written. | Header bytes 36–67, exactly as computed (not reversed). version | Job: 8 hex digits, the uint32 most significant first (`20000000`). | Bytes 0–3, little-endian (`00000020`). previousBlockHash | Job: 32 bytes of hex in Stratum order. | Bytes 4–35: each 4-byte word reversed in place, words kept in order (`01020304…` becomes `04030201…`). time | Solution: must equal `"0x" + job.time`; time rolling is not allowed. The uint32 most significant first. | Bytes 68–71, little-endian. bits | Job: 8 hex digits, the uint32 most significant first. | Bytes 72–75, little-endian. nonce | Solution: `"0x"` + 8 hex digits, the uint32 most significant first. Nonce 885 is `"0x00000375"`, not `"0x75030000"`. | Bytes 76–79, little-endian (`75030000`). Digest | SHA-256d: `SHA256(SHA256(header))`. Scrypt: `scrypt(header, salt = header, N=1024, r=1, p=1, 32 bytes)`. | Score | The 32-byte digest read as a little-endian number: reverse its bytes, then read as big-endian. | Valid when `score ≤ floor(2^256 ÷ amount) − 1`. Then retry with `PAYMENT-SIGNATURE` set to base64 JSON of `{x402Version: 2, resource, accepted, payload: {solution}}`. Copy `resource` and `accepted` exactly from the challenge; solution values are lowercase `0x` hex. Profile | Solution quai/sha256d-v1, quai/scrypt-v1 | `{"extraNonce2":"0x…4 bytes","time":"0x" + job.time,"nonce":"0x…4 bytes, most significant first"}` quai/kawpow-v1 | `{"nonce":"0x…8 bytes","mixHash":"0x…32 bytes"}` monero/randomx-v1 | `{"nonce":"0x…4 bytes, in blob order"}` For `monero/randomx-v1`, write your 4-byte nonce into `blob` at byte offset 39, as written (it is not reversed), and hash the blob with RandomX (Monero’s `rx/0`) keyed by the 32 bytes of `seedHash`. The score is the hash read as a little-endian number, as for SHA-256d. The solution’s `nonce` is those same 4 bytes in the same order: `x402-work-randomx` returns it ready to send. ## Worked example Check your solver against this `quai/sha256d-v1` example before paying for anything. It was produced by the reference client and confirmed by the Go verifier the facilitator runs; the site’s tests recompute it on every build. Job and price ``` amount: "4096" job.work: { "extraNonce1": "aabbccdd", "extraNonce2Size": 4, "previousBlockHash": "0102030405060708090a0b0c0d0e0f101112131415161718191a1b1c1d1e1f20", "coinbase1": "01000000010000000000000000", "coinbase2": "ffffffff0100", "merkleBranches": [ "a1a2a3a4a5a6a7a8a9aaabacadaeafb0b1b2b3b4b5b6b7b8b9babbbcbdbebfc0" ], "version": "20000000", "bits": "1d00ffff", "time": "671f8a40" } ``` Solution ``` {"extraNonce2":"0x0000002a","time":"0x671f8a40","nonce":"0x00000375"} ``` Step | Value (hex) 1. Coinbase | `01000000010000000000000000aabbccdd0000002affffffff0100` 2. SHA256d(coinbase) | `c2399d760265d3da5f3fd81620c7e5103cd9073a9e8d0e7267752381911a782a` 3. Merkle root | `SHA256d(step 2 ‖ branch)` = `a557977aa12f766e3b32c1b95fa6afa37fb866cb3eaf4eba613bd6ffa3ad172e` 4. Header (80 bytes) | `00000020` version · `04030201080706050c0b0a09100f0e0d14131211181716151c1b1a19201f1e1d` previous hash, word-reversed · `a557977aa12f766e3b32c1b95fa6afa37fb866cb3eaf4eba613bd6ffa3ad172e` merkle root · `408a1f67` time · `ffff001d` bits · `75030000` nonce 5. SHA256d(header) | `b90a83c83afae6d6a9f6456afb779760a512b8114610b67c1a41931cfb920b00` 6. Score (digest reversed) | `0x000b92fb1c93411a7cb6104611b812a5609777fb6a45f6a9d6e6fa3ac8830ab9` 7. Target for 4096 | `0x000fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff`: the score is lower, so this answer pays. Full header ``` 0000002004030201080706050c0b0a09100f0e0d14131211181716151c1b1a19201f1e1da557977aa12f766e3b32c1b95fa6afa37fb866cb3eaf4eba613bd6ffa3ad172e408a1f67ffff001d75030000 ``` Sending the nonce with its bytes reversed, `"0x75030000"`, puts `00000375` in the header and scores `0x0035fb5e1451ddfd18474d34b1f7dd3018b1a8853172b560b2661652ea83647b`, above the target: the server answers with a fresh 402 and `invalid_work_target`. For `quai/scrypt-v1`, the same header scores `0x6029ca40c479e2aaed12ce07fdcfb06d46c13e324af272efa8194fe859a7fb96`, which misses this target. ## Read the receipt A paid response is an ordinary `200`, or a stream, with `PAYMENT-RESPONSE`: base64 JSON of `{success, transaction, network, amount, extra}`. `transaction` fingerprints the proof; it is not a chain transaction. `amount` counts expected guesses, not coins. `extra.sourceAccepted` says whether the answer was also good enough for the network. A proof is spent when the server settles it, after verification. If the service then fails, the work is not refunded. ## Troubleshooting What you see | What to do A fresh 402 with `invalid_work_target` | The score missed the target. Most often this is byte order: send the nonce most significant byte first and check your solver against the worked example above. Solve the new job in the response. A fresh 402 with `invalid_work_stale` | The job expired before your retry arrived. Solve the new job, and stop at `refreshAfter` or `expiresAt`. A fresh 402 with `invalid_work_state` | That proof was already spent. Each proof pays once; solve the new job. “payment does not match this resource” | The request or the copied offer changed between calls. Send the request identically and copy `resource` and `accepted` unchanged. A fresh 402 after a correct answer, every time | The seller’s servers may not share the jobs they issue, so your retry reached one that didn’t issue yours. Solve the new job and retry once more; if it keeps happening, tell the seller. It is a server configuration problem you can’t fix from the client. 409 from a marketplace tool | Your proof already paid for this purchase. Recover the result with its `resultKey` instead of paying again. 429 | Too many requests. Wait the number of seconds in `Retry-After`. 503 | The seller’s work source or the facilitator is unavailable, so no job was issued or nothing was spent. Nothing was charged. Wait the number of seconds in `Retry-After` (also `error.retryAfterSeconds` in the JSON body) and send the request again; you’ll get a fresh 402. --- # Accept work payments Put a work price on any HTTP route. The work is built for your Quai address, and you run no node, verifier or database. ## Charge through the facilitator The x402 Work facilitator does the blockchain side for you. You give it your Cyprus-1 QUAI address (or, for RandomX, your Monero address) and a price; it issues each buyer a job built for that address, then verifies and spends the answer exactly once. There is no account to open. 1. No payment yet: Ask the facilitator for requirements and send them back as your `402`. 2. A payment arrives: Pass the `PAYMENT-SIGNATURE` header to the facilitator to settle it. 3. Settled: Serve the response with the `PAYMENT-RESPONSE` receipt. If settlement fails, send a fresh 402. 4. Facilitator unavailable: `charge` throws a `WorkSellerError` with `status`, `retryAfter` (seconds) and `code`: `facilitator_unavailable`, `facilitator_unreachable`, `rate_limited` or `invalid_request`. Nothing was charged. Answer `503` with `Retry-After` so buyers know when to try again. Express ``` import express from "express"; import { workSeller, WorkSellerError } from "x402-work-client"; const seller = workSeller({ payTo: process.env.REWARD_ADDRESS, // your Cyprus-1 QUAI address amount: 65536, // expected guesses: about a second }); const app = express(); app.get("/report", async (req, res, next) => { let result; try { result = await seller.charge({ url: "https://api.example.com/report", description: "Daily report", paymentSignature: req.get("payment-signature"), }); } catch (error) { if (!(error instanceof WorkSellerError)) return next(error); // The facilitator is unreachable, has no live work source or is rate-limiting. // Nothing was charged: tell the buyer when to retry. return res.status(503).set("retry-after", String(error.retryAfter ?? 30)) .json({ error: error.message }); } if (!result.paid) return res.status(402).set(result.headers).json(result.body); res.set(result.headers).json(await dailyReport()); }); app.listen(8080); ``` Install ``` npm install https://x402.qu.ai/x402-work-client-0.2.0.tgz ``` `workSeller` checks the settlement against the address, price and resource you charge for, so a proof bought for something cheaper can’t pay for this. ## Where the work goes Quai is solo-mineable, so each job’s coinbase pays your `payTo` address. Most answers are far easier than a network share: they prove the buyer did the work and unlock your response, and that is all. When an answer is good enough to count on the network, the facilitator submits it and the reward goes straight to your address. RandomX jobs come from a Monero pool that the facilitator logs in to as your Monero address. Answers good enough for the pool’s share target are submitted there, and the pool credits and pays your address under its own payout rules. | Funds | x402 Work never holds or forwards money. Rewards are paid by the network to your address. Trust | You rely on the facilitator to verify honestly and to spend each proof once, as with any x402 facilitator. Run it yourself if you’d rather not. Receipts | `extra.sourceAccepted` records the work source’s acknowledgement. It does not prove a network payout. ## Work profiles The hosted facilitator uses StratumX to obtain live Quai jobs for your `payTo` address. It keeps its own native verifier and durable spend-once records. StratumX handles upstream job creation and submission. This site hosts `quai/sha256d-v1`, `quai/scrypt-v1`, `quai/kawpow-v1` on `quai:9`; `monero/randomx-v1` on `monero:418015bb9ae982a1975da7d79277c270`. Profile | Algorithm | Who can pay quai/sha256d-v1 | SHA-256d | Any buyer: `x402-work-client`, the browser demo solver, CPUs, GPUs and ASICs. quai/scrypt-v1 | Scrypt | `x402-work-client` pays it on a CPU, slowly; keep the price near 4,096 for CPU buyers. Scrypt ASICs are much faster. quai/kawpow-v1 | KawPoW | Only buyers with a GPU miner. The reference client can’t pay it, so offer it alongside SHA-256d, not instead of it. monero/randomx-v1 | RandomX | CPU buyers with the `x402-work-randomx` solver. Price in RandomX guesses: a CPU makes hundreds to thousands a second, so a few thousand is a few seconds. `payTo` is your Monero address. Choose the profile in `workSeller({payTo, amount, profile})` or in the requirements request. Native forwarding additionally checks the source’s recorded job target; a valid access proof alone is insufficient. `/v1/facilitator/supported` lists the profiles that are live right now. ## Facilitator API No authentication. Requests are rate-limited per visitor and per payout address. GET https://x402.qu.ai/v1/facilitator/supported Schemes, network and work profiles on offer POST https://x402.qu.ai/v1/facilitator/requirements 201: an offer for one request Request ``` {"payTo":"0x00…","amount":"65536","profile":"quai/sha256d-v1", "resource":{"url":"https://api.example.com/report","description":"Daily report","mimeType":"application/json"}} ``` Response ``` {"paymentRequired":{"x402Version":2,"resource":{…},"accepts":[{…,"maxTimeoutSeconds":15,…}]}, "paymentRequiredHeader":"eyJ4NDAy…","challengeId":"wch_…","expiresAt":"2026-…"} ``` Field | Rule payTo | Required. Your Cyprus-1 QUAI address: `0x00` and 38 more hex digits, all lowercase or with a valid checksum. For `monero/randomx-v1`, your Monero mainnet address (95 characters starting with `4`, or `8` for a subaddress), checked against its checksum and matched exactly. amount | Required. The price in expected guesses, as a decimal string such as `"65536"`. There is no default. profile | Optional, default `quai/sha256d-v1`. Must be live here: `quai/sha256d-v1`, `quai/scrypt-v1`, `quai/kawpow-v1`, `monero/randomx-v1`. resource | Required. `{url, description?, mimeType?}`, or the URL as a string. HTTPS without credentials, query or fragment; HTTP only for localhost. `description` up to 200 characters, `mimeType` up to 100. Input is validated first: a missing or invalid field gets `400` naming it, even when no work source is live. Send `paymentRequiredHeader` as `PAYMENT-REQUIRED` and `paymentRequired` as the body of your 402. The description gains a short tag for legacy callers. POST https://x402.qu.ai/v1/facilitator/verify Checks a proof without spending it POST https://x402.qu.ai/v1/facilitator/settle Verifies and spends a proof once, and submits it if it qualifies Standard request ``` {"x402Version":2,"paymentPayload":{"x402Version":2,"resource":{…},"accepted":{…},"payload":{"solution":{…}}}, "paymentRequirements":{…}} ``` Legacy workSeller request ``` {"paymentSignature":"", "payTo":"0x00…","amount":"65536","resource":"https://api.example.com/report"} ``` Settled ``` {"success":true,"transaction":"0x…","network":"quai:9","amount":"65536", "extra":{"profile":"quai/sha256d-v1","sourceAccepted":false}, "paymentResponseHeader":"eyJzdWNj…"} ``` Rejected ``` {"success":false,"transaction":"","network":"quai:9","errorReason":"invalid_work_payload", "errorMessage":"paymentRequirements.amount differs from the offer the facilitator issued. …"} ``` `{"isValid":false}` and `{"success":false}` come back with HTTP `200`: they are answers, not errors. `invalidReason` and `errorReason` are codes; `invalidMessage` and `errorMessage` explain them. Status | Meaning 400 | A missing or invalid `payTo`, `amount`, `resource.url`, `profile` or resource metadata (the message names the field), or, for legacy requests, a proof issued for a different `payTo`, `amount` or `resource`. 404 | Legacy requests: the job isn’t one the facilitator issued. 429 | Too many requests. Wait the number of seconds in `Retry-After`. 503 | No live work source, or it couldn’t issue a job. No offer was issued and nothing was charged. Wait the number of seconds in `Retry-After` (also `error.retryAfterSeconds`) and retry. ## Matching rules Standard x402 SDKs send `{x402Version: 2, paymentPayload, paymentRequirements}`. The facilitator finds the offer it issued by `extra.job.id`, then requires your `paymentRequirements` and the buyer’s `paymentPayload.accepted` to match that offer on every field that decides what is paid, to whom and for what: Field | Rule scheme, network, asset, amount | Must be identical. `amount` is compared as a string. payTo | Must be the same address. Letter case is ignored, so a checksummed address matches the lowercase one in the offer. extra.name, extra.version, extra.profile | Must be identical. extra.job (id, expiresAt, refreshAfter, work) | Must be identical: the job binds the work to your address and sets its expiry. paymentPayload.resource.url | When present, must be the URL the offer was issued for. Descriptions and MIME types are display metadata and may differ. maxTimeoutSeconds, other fields | Ignored. The facilitator verifies against its own offer, which uses `15`. When something differs, verify returns `{"isValid":false,"invalidReason":"invalid_work_payload","invalidMessage":"paymentRequirements.amount, paymentRequirements.payTo differ from the offer the facilitator issued. …"}`, and settle returns the same in `errorReason` and `errorMessage`. Fix the named fields; don’t copy the buyer’s values into your requirement. `maxTimeoutSeconds` is how long a buyer may take between the 402 and the paid retry. A job’s `expiresAt` is never later than the offer’s time plus `maxTimeoutSeconds`, and hosted jobs last 15 seconds, so use `15` in your route configuration. A larger value is accepted but doesn’t make a job last longer. ## Set a price The price is the expected number of guesses. A laptop browser makes roughly 50,000 SHA-256d guesses a second, so `65536` is about a second of work and `1048576` about twenty. Scrypt is about a hundred times slower per guess on a CPU. Price each route by what it costs you to serve and how much friction you want. ## Run it yourself To verify and settle on your own servers instead, run a Quai Stratum source for your address, the Go Quai verifier, a durable store that spends each proof once, and the `x402-work-http` gate: Install ``` npm install https://x402.qu.ai/x402-work-http-0.1.0.tgz https://x402.qu.ai/x402-work-provider-0.1.0.tgz ``` Node ``` import http from "node:http"; import { createWorkGate } from "x402-work-http"; import { createStratumProfiles, GoQuaiVerifier } from "x402-work-provider"; const payTo = process.env.REWARD_ADDRESS; // your Cyprus-1 QUAI address const gate = createWorkGate({ network: "quai:9", payTo, maxTimeoutSeconds: 15, // jobs expire at most 15 seconds after the 402 profiles: createStratumProfiles({ algorithms: ["sha256d"], payTo, network: "quai:9", verifier: new GoQuaiVerifier(process.env.QUAI_WORK_VERIFIER_BIN), workshareAmounts: { sha256d: process.env.SHA256D_WORKSHARE_AMOUNT }, endpoints: { sha256d: process.env.SHA256D_STRATUM_URL }, }), settlementStore, // durable and atomic: each proof is spent once }); http.createServer(async (req, res) => { let result; try { result = await gate.authorize(req, { price: { "quai/sha256d-v1": "65536" }, // expected guesses, not coins resource: { url: "https://api.example.com/report", description: "Daily report", mimeType: "application/json" }, }); } catch { // The work source couldn't issue a job. Nothing was charged. res.writeHead(503, { "retry-after": "15" }).end(); return; } if (!result.paid) { res.writeHead(402, result.headers).end(JSON.stringify(result.paymentRequired)); return; } res.writeHead(200, { ...result.headers, "content-type": "application/json" }).end(JSON.stringify(report())); }).listen(8080); ``` Both packages are served from this site, not the npm registry; see [Libraries](https://x402.qu.ai/docs/libraries) for versions and sha256 digests. `x402-work-provider` includes the Go verifier’s source; build it with `go build` in `native-verifier/` and pass the binary to `GoQuaiVerifier`. ## Get listed Listings link to services that run on their own sites. To be listed in the [Marketplace](https://x402.qu.ai/marketplace), contact the x402 Work team with your service’s URL, endpoint, category and reward address. Domain ownership is verified with a DNS TXT record before a listing goes live. --- # Facilitators A facilitator verifies and settles x402 payments for a seller. Use a hosted one, or run your own with the libraries. ## Hosted facilitators Facilitator | Scheme | Networks | What it settles | Address x402 Work | `work` | `quai:9`, `monero:418015bb9ae982a1975da7d79277c270` | Proof of work: SHA-256d, Scrypt, KawPoW, RandomX jobs for your address | `https://x402.qu.ai/v1/facilitator` Quai exact | `exact` | `quai:9`, `quai:15000` | Direct native QUAI and Qi in Cyprus-1; sender pays fees | `https://x402.qu.ai/v1/quai` The networks above are what this deployment is configured for. Each facilitator also answers `GET …/supported` with what it can settle right now; check it before you issue a requirement. `https://x402.qu.ai/.well-known/x402` lists both, with the paid resources on this site. ## x402 Work facilitator Settles the `work` scheme on Quai: it issues jobs built for your payout address, verifies each answer and spends it once. No account, and it never holds funds. The API is in [Accept work payments](https://x402.qu.ai/docs/sell). `verify` and `settle` accept the standard x402 request, `{x402Version:2, paymentPayload, paymentRequirements}`, so the official `HTTPFacilitatorClient` can point here. The requirements must come from your trusted route and the hosted-issued job. Legacy `workSeller` requests keep working. Discovery ``` curl https://x402.qu.ai/v1/facilitator/supported ``` Response ``` {"kinds":[{"x402Version":2,"scheme":"work","network":"quai:9", "extra":{"name":"x402-work","version":"1", "profiles":["quai/sha256d-v1","quai/scrypt-v1","quai/kawpow-v1","monero/randomx-v1"]}}], "extensions":[],"signers":{}} ``` Standard requests must match the issued offer on the fields listed in [Matching rules](https://x402.qu.ai/docs/sell/matching); `payTo` may differ in letter case and `maxTimeoutSeconds` is ignored. ## Quai exact facilitator Base URL: `https://x402.qu.ai/v1/quai`. This deployment settles mainnet (`quai:9`) and Orchard testnet (`quai:15000`) from the same endpoint. It settles direct native QUAI (`slip44:994`, wei) and Qi (`slip44:969`, qits) in Cyprus-1 (`0x00`). The sender signs the canonical transaction and pays its fees; the facilitator verifies, broadcasts, waits for inclusion and records the transaction durably. There is no facilitator account or API key. Sponsored QUAI and ERC-20 transfers are described in the scheme specification but are not enabled on this hosted endpoint. GET https://x402.qu.ai/v1/quai/supported Enabled networks, native assets, zones and fee policy POST https://x402.qu.ai/v1/quai/verify {x402Version:2, paymentPayload, paymentRequirements} → {isValid, invalidReason, payer?}; never broadcasts POST https://x402.qu.ai/v1/quai/settle Same body → {success, transaction, network, payer?} after inclusion Discovery ``` curl https://x402.qu.ai/v1/quai/supported ``` Kinds (one per enabled network) ``` {"x402Version":2,"scheme":"exact","network":"quai:9", "extra":{"areFeesSponsored":false,"assets":["slip44:994","slip44:969"], "zones":["0x00"],"maxTimeoutSeconds":120}} ``` Resource server ``` import { HTTPFacilitatorClient } from "@x402/core/http"; import { x402ResourceServer } from "@x402/core/server"; import { ExactQuaiScheme } from "@x402/quai/exact/server"; // Settlement waits for inclusion: allow your HTTP stack up to 150 seconds. const client = new HTTPFacilitatorClient({ url: "https://x402.qu.ai/v1/quai" }); const server = new x402ResourceServer(client) .register("quai:*", new ExactQuaiScheme()); await server.initialize(); ``` Settlement succeeds only after inclusion, not mempool acceptance. Qi settlements omit `payer`; Qi prices must equal one native denomination, with a fresh recipient address per payment. If submission times out, retry the same signed payload to reconcile it; never automatically create a second payment. ## Standard work resource server Use the standard SDK with a remote job issuer. The issuer obtains a job from this facilitator for your configured address and price; you run no node or verifier. Hosted jobs expire 15 seconds after they are issued, so set `maxTimeoutSeconds: 15` on the route. `payTo` may be checksummed. Resource URLs must be HTTPS (HTTP localhost is allowed for tests). TypeScript ``` import { HTTPFacilitatorClient } from "@x402/core/http"; import { x402ResourceServer } from "@x402/core/server"; import { WorkServerScheme } from "@x402/work/server"; const profile = "quai/sha256d-v1"; const issuer = { profile, asset: "hash", networks: ["quai:9"], async issue({ requirements, resource }) { const r = await fetch("https://x402.qu.ai/v1/facilitator/requirements", { method: "POST", headers: {"content-type":"application/json"}, body: JSON.stringify({ payTo: requirements.payTo, amount: requirements.amount, profile, resource }), }); // 503: no live work source; nothing was charged. Surface it as a 503 with Retry-After. if (!r.ok) throw new Error((await r.json()).error?.message ?? r.statusText); return (await r.json()).paymentRequired.accepts[0].extra.job; }, }; const server = new x402ResourceServer(new HTTPFacilitatorClient({ url: "https://x402.qu.ai/v1/facilitator", })).register("quai:9", new WorkServerScheme({issuers:[issuer]})); await server.initialize(); // Route: scheme "work", network "quai:9", payTo your address, // maxTimeoutSeconds:15, // price:{asset:"hash", amount:"65536", extra:{profile}}. ``` The SDK sends `{x402Version:2, paymentPayload, paymentRequirements}` to verification and settlement. Always verify against your server’s own requirements, never an unchecked buyer-supplied price. SHA-256d, Scrypt and KawPoW use the same API; the buyer needs a solver for the chosen algorithm, and `x402-work-client` solves SHA-256d and Scrypt only. ## Run your own Both facilitators are built from the libraries in [Libraries](https://x402.qu.ai/docs/libraries): `WorkFacilitatorScheme` from `@x402/work` and `ExactQuaiScheme` from `@x402/quai`. Production facilitators need a durable settlement store shared by every process, so a proof or a transaction is settled exactly once. --- # Libraries Pay and charge with x402 Work, or settle QUAI and Qi, from TypeScript. ## Which to use Package | What it does | For x402-work-client | Pay for any work 402 (`payWithWork`, SHA-256d and Scrypt), charge through the hosted facilitator (`workSeller`), and the `x402-work` command. | Most buyers and sellers @x402/work | The `work` scheme for the official x402 SDK: client, resource server and facilitator. | Apps on the x402 SDK. Proposed in [x402 #3](https://github.com/dominant-strategies/x402/pull/3) @x402/quai | The `exact` scheme for direct native QUAI and Qi: client, resource server and facilitator. | Accepting QUAI or Qi. Proposed in [x402 #2](https://github.com/dominant-strategies/x402/pull/2) x402-work-http | A framework-neutral HTTP gate that verifies and settles work itself. | Running your own verification x402-work-provider | Quai StratumX work profiles and the Go native verifier’s source. | Running your own work source ## Install None of these are on the npm registry. This site serves each as a versioned tarball; install it by URL, the same way for every package. Pin the version, and check the sha256 if you vendor the file. Their dependencies, including `@x402/core` 2.20, come from npm. Package | Version | Install | sha256 x402-work-client | 0.2.0 | `npm install https://x402.qu.ai/x402-work-client-0.2.0.tgz` | `92586052e94f225a9a80ee68d8faec08b5d9903392466301219c3ee0787fc4ab` @x402/work | 2.20.0 | `npm install https://x402.qu.ai/x402-work-2.20.0.tgz` | `554edb03bfc432a4b06c68b5df6a714b512f8eeb5242de9b2a2bf874f99c7b20` @x402/quai | 2.20.0 | `npm install https://x402.qu.ai/x402-quai-2.20.0.tgz` | `a9c38518f91554ec943a7b4d29bc4d1f26dcdf5036a90b54ec1bc67131ae5353` x402-work-http | 0.1.0 | `npm install https://x402.qu.ai/x402-work-http-0.1.0.tgz` | `e7b6c3d97e842bc059c4c271bcc4da27ed6ae5e61c36146301864f99a7cb2f16` x402-work-provider | 0.1.0 | `npm install https://x402.qu.ai/x402-work-provider-0.1.0.tgz` | `9220ca1f7bf1165941651ba20e22a1b893f526afda2746e5deffb59e115ec521` `https://x402.qu.ai/x402-packages.json` lists the same files with npm integrity strings. `x402-work-http` and `x402-work-provider` depend on `@x402/work` and fetch it from this site automatically. ## x402-work-client Install ``` npm install https://x402.qu.ai/x402-work-client-0.2.0.tgz ``` Pay ``` import { payWithWork } from "x402-work-client"; const { response, payment } = await payWithWork(url, body, { maxPrice: 500_000 }); ``` Charge ``` import { workSeller, WorkSellerError } from "x402-work-client"; const seller = workSeller({ payTo: "0x00…", amount: 65536 }); const result = await seller.charge({ url, paymentSignature }); // throws WorkSellerError when the facilitator is down ``` Also exported: `workOffer`, `solveWork`, `workHeader`, `workDigest`, `workScore`, `workTarget`, `decodeHeader` and `encodeHeader`. Full guides: [Pay with work](https://x402.qu.ai/docs/pay) and [Accept work payments](https://x402.qu.ai/docs/sell). ## @x402/work Registers the `work` scheme with the x402 SDK. A profile owns the native job, proof function and source submission; the package owns the x402 flow. Install ``` npm install https://x402.qu.ai/x402-work-2.20.0.tgz @x402/core@2.20.0 ``` Resource server ``` import { WorkServerScheme } from "@x402/work/server"; resourceServer.register("quai:9", new WorkServerScheme({ issuers: [profile] })); const price = { amount: "65536", asset: "hash", extra: { profile: "quai/sha256d-v1" }, }; ``` Client ``` import { WorkClientScheme } from "@x402/work/client"; client.register("quai:9", new WorkClientScheme({ solvers: [profileSolver] })); ``` Facilitator ``` import { WorkFacilitatorScheme } from "@x402/work/facilitator"; facilitator.register( "quai:9", new WorkFacilitatorScheme({ profiles: [profile], store: durableSharedStore }), ); ``` The solver gets the job, the derived target and an `AbortSignal` that fires at `refreshAfter` (or `expiresAt`). `InMemoryWorkSettlementStore` is for tests and single-process examples only. ## @x402/quai Install ``` npm install https://x402.qu.ai/x402-quai-2.20.0.tgz @x402/core@2.20.0 quais@1.0.0-alpha.55 ``` Client ``` import { x402Client } from "@x402/core/client"; import { ExactQuaiScheme } from "@x402/quai/exact/client"; // signPayment returns the canonical payer-signed transaction for the requirement: // a type 0 QUAI transfer, or a type 2 Qi transaction with one output to payTo. const signer = { async signPayment(requirements) { return signedTransactionHex; } }; const client = new x402Client(); client.register("quai:*", new ExactQuaiScheme(signer)); ``` Resource server ``` import { x402ResourceServer } from "@x402/core/server"; import { ExactQuaiScheme } from "@x402/quai/exact/server"; const server = new x402ResourceServer(facilitatorClient); server.register("quai:*", new ExactQuaiScheme()); // Route price: 0.0001 QUAI, in wei { scheme: "exact", network: "quai:9", payTo: "0x00…", price: { asset: "slip44:994", amount: "100000000000000" } } ``` Facilitator ``` import { x402Facilitator } from "@x402/core/facilitator"; import { ExactQuaiScheme } from "@x402/quai/exact/facilitator"; const facilitator = new x402Facilitator(); facilitator.register("quai:*", new ExactQuaiScheme({ provider }, durableSettlementStore)); ``` For Qi, price in `slip44:969` with one native qit denomination. This build covers direct, payer-funded QUAI and Qi, the same as the hosted facilitator. The proposal’s sponsored QUAI escrow, `createQuaiPaymentSigner` and the ERC-20 adapters in `@x402/quai/evm` are not included; ERC-20 tokens on Quai can use `@x402/evm` on `eip155:9` directly. --- # The work scheme Pay for an x402 resource with one proof-of-work solution. A profile defines the job, the proof and the score; the scheme defines everything else. ## Summary The 402 carries a current native mining job. The client returns a profile-defined solution, and payment succeeds when its score meets the target derived from `amount`. If the same solution also meets the network’s own target, the provider submits it for native credit. Pools, node RPC, Stratum and StratumX are adapters behind a profile, not part of it. Specification: [`specs/schemes/work/scheme_work.md`](https://github.com/dominant-strategies/x402/pull/3/files) and [`profile_quai.md`](https://github.com/dominant-strategies/x402/pull/3/files) in x402 #3. ## Payment requirements accepts[] ``` { "scheme": "work", "network": "quai:9", "asset": "hash", "amount": "65536", "payTo": "0x0123456789abcdef0123456789abcdef01234567", "maxTimeoutSeconds": 15, "extra": { "name": "x402-work", "version": "1", "profile": "quai/sha256d-v1", "job": { "id": "source-job-42", "expiresAt": 1770000015, "refreshAfter": 1770000004, "work": { "extraNonce1": "01020304", "extraNonce2Size": 4, "previousBlockHash": "00…", "coinbase1": "0102", "coinbase2": "000000000304", "merkleBranches": [], "version": "20000000", "bits": "1d00ffff", "time": "65b00000" } } } } ``` Field | Rule x402Version, scheme | `2` and `"work"`. network | The CAIP-2 network receiving the work, such as `quai:9`. asset | Always `"hash"`. amount | A base-10 integer from 1 to 2^256: the expected number of profile trials. payTo | The reward destination the job is bound to. maxTimeoutSeconds | The most seconds between the offer and the paid retry. `extra.job.expiresAt` must not be later than the offer’s time plus this; a facilitator rejects a job that outlives it. This site’s offers use `15`. extra.name, extra.version | `"x402-work"` and `"1"`. The profile carries its own revision (`-v1`). extra.job | `id` (1–128 characters), `expiresAt`, optional earlier `refreshAfter`, and `work`: every public value needed to build a candidate. Source credentials never leave the server. Target ``` target = floor(2^256 / amount) - 1 valid = score <= target ``` The target is derived, never sent. Work from different profiles is not interchangeable. ## Payment payload Retry the same request with `PAYMENT-SIGNATURE`: base64 JSON of the x402 v2 payload, with the chosen requirement copied unchanged into `accepted`. PAYMENT-SIGNATURE (decoded) ``` { "x402Version": 2, "resource": { … }, "accepted": { …the requirement… }, "payload": { "solution": { "extraNonce2": "0x00000001", "time": "0x65b00000", "nonce": "0x00000002" } } } ``` Stop solving at `refreshAfter` or `expiresAt` and ask again without payment. ## Profiles A versioned profile defines the networks and asset, strict job and solution schemas, candidate construction, proof validation, a uint256 score, a cheap fingerprint, freshness and reward binding, native submission, and a stable work identifier. Profile | Algorithm | Networks the profile defines | Status quai/sha256d-v1 | SHA-256d | `quai:9`, `quai:15000` | Hosted here on `quai:9` quai/scrypt-v1 | Scrypt (N=1024, r=1, p=1) | `quai:9`, `quai:15000` | Hosted here on `quai:9` quai/kawpow-v1 | KawPoW | `quai:9`, `quai:15000` | Hosted here on `quai:9` monero/randomx-v1 | RandomX (`rx/0`) | `monero:418015bb9ae982a1975da7d79277c270` | Hosted here on `monero:418015bb9ae982a1975da7d79277c270` bitcoin/sha256d-v1, zcash/equihash-200-9-v1 | Example identifiers in the spec | | Not yet defined A profile can define several networks; a facilitator hosts the ones it is configured for. Check its `/supported`. ## Quai profiles SHA-256d and Scrypt jobs are a Stratum job plus its subscription values; KawPoW jobs are one `mining.notify`. Hex inside `job.work` is lowercase without `0x`; solution fields use a lowercase `0x` prefix. SHA-256d and Scrypt job ``` { "extraNonce1": "01020304", "extraNonce2Size": 4, "previousBlockHash": "00…32 bytes, Stratum word-swapped…", "coinbase1": "0102", "coinbase2": "000000000304", "merkleBranches": ["11…32 bytes"], "version": "20000000", "bits": "1d00ffff", "time": "65b00000" } ``` Solution ``` { "extraNonce2": "0x00000001", "time": "0x65b00000", "nonce": "0x00000002" } ``` KawPoW job ``` { "headerHash": "11…32 bytes", "seedHash": "22…32 bytes", "height": 123456, "bits": "1d00ffff" } ``` Solution ``` { "nonce": "0x0000000000000001", "mixHash": "0x33…32 bytes" } ``` | Rule Header | 80 bytes: version, time, bits and nonce as little-endian uint32s; `previousBlockHash` with each 4-byte word reversed; the merkle root as computed. Solution `time` and `nonce` are the uint32 written most significant byte first. Field by field, with a worked example, in [Pay with work](https://x402.qu.ai/docs/pay/solve). SHA-256d | `SHA256d(header)` over that header. Time and version rolling are not allowed. Scrypt | `scrypt(header, header, N=1024, r=1, p=1, dkLen=32)` over the same header. KawPoW | Quai KawPoW over `headerHash`, the nonce and `height`. `mixHash` must equal the digest; the result is the score. Score | SHA-256d and Scrypt: the digest reversed, read as a big-endian integer. KawPoW: the result, big-endian. Fingerprint | `SHA256(profile ‖ 0x00 ‖ network ‖ 0x00 ‖ candidate)`; the candidate is the header, or `headerHash ‖ nonce ‖ mixHash`. It is the settlement `transaction`. ## Monero profile `monero/randomx-v1` jobs are Monero pool jobs (`login`, `job` and `submit` JSON-RPC) for a pool session logged in as `payTo`, a Monero address. The network is `monero:418015bb9ae982a1975da7d79277c270`: `monero` and the first 32 hex digits of the mainnet genesis block hash, as CAIP-2 does for Bitcoin. Job ``` { "blob": "1010…76 bytes, the Monero hashing blob…", "seedHash": "6f…32 bytes, the RandomX key…", "height": 3500000 } ``` Solution ``` { "nonce": "0x1c0e0b2f" } ``` | Rule Algorithm | RandomX as Monero mainnet uses it today (`rx/0`, RandomX v1), keyed by `seedHash`. Jobs for any other algorithm are refused, so a fork that changes it needs a new profile version. Candidate | `blob` with the solution’s 4 nonce bytes written at offset 39, in order. Score | The RandomX hash read as a little-endian integer, as Monero compares it. Fingerprint | `SHA256(profile ‖ 0x00 ‖ network ‖ 0x00 ‖ seedHash ‖ candidate)`. Reward binding | The pool session is logged in as `payTo`; an answer at or below the pool’s share target for the job is submitted on that session. Freshness | Jobs live for at most 60 seconds, and `refreshAfter` is 45. ## Discovery A facilitator advertises only profiles whose job, proof, freshness and reward binding it can fully verify. Servers confirm a profile is supported before issuing a job. GET /supported ``` { "x402Version": 2, "scheme": "work", "network": "quai:9", "extra": { "name": "x402-work", "version": "1", "profiles": ["quai/sha256d-v1", "quai/scrypt-v1", "quai/kawpow-v1"] } } ``` ## Verification and settlement | Verify | Fails unless `accepted` matches the trusted requirement (this site’s facilitator compares the fields in [Matching rules](https://x402.qu.ai/docs/sell/matching)) and the profile, network, asset, target, job, reward binding, proof, score, freshness and replay state are all valid. Verification never consumes or submits work. Settle | Atomically consumes the fingerprint and its requirement binding in a durable store shared by every facilitator process. Exactly one concurrent settlement succeeds. Response | Omits `payer`; copies `network` and `amount`; `transaction` is the fingerprint. `extra.sourceAccepted` is diagnostic only. Native credit | When the score also meets the network target, the provider should submit the exact solution through the session that issued the job. A failed submission does not undo a valid payment. Miners | A client can hand the job to ordinary GPU or ASIC miners through a local Stratum adapter that converts `mining.submit` into the solution. ## Errors Reason | Meaning invalid_work_payload | Envelope, job, solution or profile data is malformed invalid_work_profile | The facilitator does not support the selected profile invalid_work_stale | The job expired or became stale invalid_work_target | The score does not meet the derived target invalid_work_state | The fingerprint was already consumed before verification invalid_work_settled | Settlement found the same binding already consumed invalid_work_conflict | Settlement found the fingerprint under another binding --- # Exact payments on Quai Standard x402 exact payments in native QUAI or Qi: a specific amount from the client to the seller, settled on Quai. ## Summary Direct payments carry a complete payer-signed Quai transaction. Native QUAI can instead use an escrow authorization, so the facilitator pays the gas without being able to change the destination or amount. Qi is direct only. This version supports same-zone transfers. Specification: [`specs/schemes/exact/scheme_exact_quai.md`](https://github.com/dominant-strategies/x402/pull/2/files) in x402 #2. ## Networks and assets Network | CAIP-2 Quai Mainnet | `quai:9` Orchard Testnet | `quai:15000` Asset | Identifier | Atomic unit | Transaction QUAI | `slip44:994` | wei, 10^-18 QUAI | Account ledger, type 0, ECDSA Qi | `slip44:969` | qit, 10^-3 Qi | UTXO ledger, type 2, Schnorr or MuSig The zone comes from the transaction addresses and is never appended to the network identifier. ## Payment requirements Direct QUAI ``` { "scheme": "exact", "network": "quai:9", "asset": "slip44:994", "amount": "1000000000000000", "payTo": "0x0000…0001", "maxTimeoutSeconds": 60, "extra": { "areFeesSponsored": false } } ``` Sponsored QUAI ``` { "scheme": "exact", "network": "quai:9", "asset": "slip44:994", "amount": "1000000000000000", "payTo": "0x0000…0001", "maxTimeoutSeconds": 60, "extra": { "areFeesSponsored": true, "assetTransferMethod": "escrow", "settlementContract": "0x0000…0003" } } ``` Qi ``` { "scheme": "exact", "network": "quai:9", "asset": "slip44:969", "amount": "1", "payTo": "0x0080…0001", "maxTimeoutSeconds": 60, "extra": { "areFeesSponsored": false } } ``` A Qi amount is one native denomination: 1, 5, 10, 50, 100, 500, 1000, 5000, 10000, 20000, 100000, 1000000, 10000000, 100000000 or 1000000000 qits. Use a fresh Qi payTo for each payment, and the same one for every retry of it. ## Payment payload Direct QUAI or Qi ``` { "x402Version": 2, "accepted": { … }, "payload": { "signedTransaction": "0x…canonical protobuf…" } } ``` Sponsored QUAI ``` { "x402Version": 2, "accepted": { … }, "payload": { "signature": "0x…", "authorization": { "from": "0x…", "to": "0x…", "value": "1000000000000000", "validAfter": "1770000000", "validBefore": "1770000060", "nonce": "0x…" } } } ``` The sponsored authorization is EIP-712 signed with domain `{name: "x402 Native QUAI", version: "1", chainId, verifyingContract: settlementContract}`. The payer deposits QUAI into the escrow once, then signs bounded authorizations. ## Verification Payment | Checks Envelope | x402 v2, scheme `exact`, matching supported `quai` network and asset, and exact `amount`, `payTo` and `maxTimeoutSeconds`. The protobuf re-encodes to the same bytes and its chain ID matches the network. QUAI | Type 0, canonical ECDSA, same zone, `to` = `payTo`, `value` = `amount`, no data, the next pending nonce, a sufficient balance and a successful simulation. `payer` comes from the signature. Sponsored QUAI | The trusted escrow for the network and zone, same-zone addresses, an active validity window, a canonical EIP-712 signature, an unused nonce and a successful `transferWithAuthorization` simulation. Qi | Type 2, unspent and unlocked inputs from one zone, valid denominations, no address reuse, exactly one output to `payTo` worth `amount`, enough fee, and a Schnorr or MuSig signature. No `payer` is returned. ## Settlement The facilitator reserves the transaction hash atomically, re-verifies, submits the original bytes with `quai_sendRawTransaction` to the origin zone, and returns success only after inclusion. Sponsored QUAI calls the escrow’s `transferWithAuthorization` and pays the gas. Settled ``` { "success": true, "errorReason": null, "payer": "0x…", "transaction": "0x", "network": "quai:9" } ``` ## ERC-20 tokens on Quai ERC-20 tokens on Quai’s account ledger use the standard `exact` EVM scheme on `eip155:9` (or `eip155:15000` for Orchard), with the token contract as `asset`. EIP-3009 and Permit2 payloads keep their EVM wire format; Permit2 and its proxy are resolved per zone. The hosted facilitator on this site does not settle ERC-20 tokens or sponsored QUAI. ## Errors Error | Meaning invalid_exact_quai_asset | Unsupported native asset invalid_exact_quai_network | Unsupported or mismatched network invalid_exact_quai_encoding | Invalid or non-canonical protobuf invalid_exact_quai_signature | ECDSA, Schnorr or MuSig verification failed invalid_exact_quai_destination | Payment destination differs invalid_exact_quai_amount | Payment amount differs invalid_exact_quai_zone | Transfer is not same-zone, or the zone is inactive invalid_exact_quai_sponsorship | Sponsored method or contract is invalid invalid_exact_quai_nonce | QUAI nonce is not the next pending nonce invalid_exact_quai_input | A Qi input is missing, spent, locked, duplicated or unauthorized invalid_exact_quai_output | A Qi output breaks denomination, address or lock rules invalid_exact_quai_fee | Network fee is too low invalid_exact_quai_simulation | Simulation failed invalid_exact_quai_settlement | Broadcast or inclusion failed --- # Inference with Hashference Hashference, the first service on x402 Work, serves open models paid per request with work. ## Endpoints POST https://hashference.com/x402/v1/chat/completions OpenAI Chat Completions body POST https://hashference.com/x402/v1/responses OpenAI Responses body POST https://hashference.com/x402/v1/messages Anthropic Messages body The first call answers 402 with a price based on the model, the prompt and the output limit. Model IDs come from `https://hashference.com/v1/catalog`. ## Call it x402-work-client ``` import { payWithWork } from "x402-work-client"; const { response } = await payWithWork("https://hashference.com/x402/v1/chat/completions", { model: "", messages: [{ role: "user", content: "What is proof of work?" }], max_tokens: 256, }, { maxPrice: 2_000_000 }); console.log((await response.json()).choices[0].message.content); ``` hashference CLI ``` npm i -g https://hashference.com/hashference-0.1.0.tgz hashference x402 ask "What is proof of work?" ``` ## With an API key instead For steady traffic, Hashference also runs the same models from a workspace balance, funded by mining, QUAI or Qi. Create a key at [hashference.com](https://hashference.com/keys). --- # Marketplace Find services that take work, buy a hosted tool, and recover a result you paid for. ## Discover services Open [the Marketplace](https://x402.qu.ai/marketplace) or call its public API. Each listing names the publisher, category, work profile, price, method, absolute endpoint URL and current availability. Hosted tool counts are successful purchases in the last 30 days. GET https://x402.qu.ai/v1/marketplace/services GET https://x402.qu.ai/v1/marketplace/services/:slug cURL ``` curl https://x402.qu.ai/v1/marketplace/services ``` Hosted JSON and text tools run in memory; your input and output are not stored. Partner and community listings run on their own sites. ## Buy a hosted service Send `consent: true` and an input. Invalid input is rejected before any job is issued. Then pay the 402 as in [Pay with work](https://x402.qu.ai/docs/pay). POST https://x402.qu.ai/x402/v1/marketplace/:slug cURL ``` curl https://x402.qu.ai/x402/v1/marketplace/json-workbench \ -H "content-type: application/json" \ -d '{"consent":true,"input":{"action":"format","text":"{\"hello\":\"work\"}"}}' ``` ## Recover a paid result Keep `extensions["quai-marketplace"]` from the first 402 private. If a response is lost after the work settles, send the original input with its `resultKey` as a Bearer token, within ten minutes. No new work starts. POST https://x402.qu.ai/v1/marketplace/jobs/:jobId/result ## Status codes Status | Meaning 400 | Invalid or changed input, missing consent, or a malformed proof. 402 | A live challenge, or a rejected proof with a fresh challenge. 404 | Unknown service or result key. 409 | Your proof already settled this purchase (recover the result instead of paying again), or the result is still being prepared. 410 | The job or the recovery window expired. 429 | Too many requests. Wait the number of seconds in `Retry-After`. 503 | The work source is unavailable, so no job was issued. Nothing was charged. Wait the number of seconds in `Retry-After` and try again. Hosted tools answer a replayed proof with `409` because each purchase has a recoverable result. Standard x402 resources, such as the demo and sites that use `workSeller`, answer a replay with a fresh `402` and `invalid_work_state`.