# Basic usage (/docs/basic-usage) These examples call the backend directly. They assume you followed [Installation](/docs/installation), so the API is at `http://localhost:8081` and authentication is off. If your backend requires authentication, add an `Authorization: Bearer ` header to every request. ## Check that peers are available [#check-that-peers-are-available] A circuit needs worker peers. List the services they advertise: ```bash curl http://localhost:8081/api/v1/services ``` Each entry describes one service on one peer: `node_id`, `service_type` (for example `hadamard` or `cnot`), `fidelity`, the `qubit_min` and `qubit_max` it supports, and `availability`. ## Submit a circuit [#submit-a-circuit] Send OpenQASM text in the `circuit` field. This one prepares a Bell pair: ```bash curl -X POST http://localhost:8081/api/v1/circuits/submit \ -H "Content-Type: application/json" \ -d '{"circuit": "OPENQASM 2.0;\ninclude \"qelib1.inc\";\nqreg q[2];\ncreg c[2];\nh q[0];\ncx q[0],q[1];\nmeasure q -> c;"}' ``` The API answers `201 Created` right away. The work happens in the background. ```json { "job_id": "job_8d0f5af2fd3640be8d62a9f48f8d3d6d", "status": "QUEUED" } ``` ## Follow the job [#follow-the-job] Poll the job until `status` is `COMPLETED` or `FAILED`: ```bash curl http://localhost:8081/api/v1/jobs/job_8d0f5af2fd3640be8d62a9f48f8d3d6d ``` While it runs, `progress` tells you how many fragments have finished: ```json { "job_id": "job_8d0f5af2fd3640be8d62a9f48f8d3d6d", "status": "EXECUTING", "plan_id": "plan-3f7a10d6-8780-4c7f-8a3a-9cb8cf848a8e", "error": null, "result": null, "progress": { "total_fragments": 3, "completed_fragments": 1, "active_fragments": 2, "completion_ratio": 0.3333, "finalizing": false } } ``` The states a job moves through are described in [Jobs](/docs/concepts/jobs). The values above are illustrative: your `job_id`, `plan_id` and fragment counts will differ. ## Read the result [#read-the-result] When the job is `COMPLETED`, `result.quantum_result` holds the outcome. Among other fields it includes the final `statevector`, `probabilities`, `top_basis_states`, `bloch_vectors`, `entanglement_entropy` and a `fidelity` summary. A circuit that measures also reports sampled `counts` and the number of `shots`. Add `?result_detail=summary` to `GET /api/v1/jobs/{job_id}` when you do not need the large fields. The default is `full`. In the summary, `probabilities`, `statevector` and `reduced_density_matrices` are `null` and everything else is unchanged. A completed job also carries a `provenance_bundle_id`. Pass it to `GET /api/v1/provenance/{bundle_id}/verify` to check the record. See [Provenance](/docs/concepts/provenance). ## Handle failures [#handle-failures] If a job ends as `FAILED`, the `error` field contains the reason. Common causes are a circuit the parser cannot read (see [Circuit planning](/docs/concepts/circuit-planning)) and no available peer for one of the required services. A request the API itself rejects, such as a `circuit` shorter than 10 characters, fails immediately with a validation error and creates no job. Credit metering is off by default (`QB2_CIRCUIT_JOB_CREDIT_COST=0`). When you turn it on, submitting a job holds its cost first. With too few credits the API answers `402 Payment Required` with the error code `insufficient_credits` and creates no job. ## List your jobs [#list-your-jobs] ```bash curl "http://localhost:8081/api/v1/jobs?limit=20&status=COMPLETED" ``` `limit` accepts 1 to 200 (default 50), and `status` can be repeated. Without admin rights you only see your own jobs. ## Next steps [#next-steps] - Understand each step in [Architecture](/docs/concepts/architecture). - Look up every endpoint in the [HTTP API reference](/docs/reference/http-api). - Charge for requests with [x402 payments](/docs/integrations/x402). # Architecture (/docs/concepts/architecture) QMesh has three kinds of process: a **coordinator**, **worker peers** and an **operator console**. They share two stores: MongoDB for durable state and an append-only peer log on disk. ## The parts [#the-parts] | Part | What it does | Built with | | --- | --- | --- | | Coordinator | Serves the HTTP API, parses and plans circuits, reserves peers, dispatches fragments, assembles results and records provenance. | Python, FastAPI | | Worker peers | Advertise the services they offer, accept reservations and execute the fragments sent to them. | py-libp2p, Qiskit | | Operator console | Shows the network, runs and results. The browser never calls the backend directly: the console's server routes proxy every request. | Next.js | | MongoDB | Jobs, execution plans, peer projections, reservation and execution events, provenance bundles. | | | Peer log | An append-only JSONL file, flushed with `fsync` by default, kept by the coordinator. | | The x402 payment gateway runs inside the **console's** server routes, not in the coordinator. It forwards paid requests to hidden internal coordinator routes protected by a shared secret. See [x402 payments](/docs/integrations/x402). ## How a circuit run flows [#how-a-circuit-run-flows] ```mermaid sequenceDiagram participant C as Client participant K as Coordinator participant P as Worker peer C->>K: POST /api/v1/circuits/submit K-->>C: 201 job_id, QUEUED Note over K: COMPILING: parse, build the dependency graph, plan loop each block, in stage order K->>P: reservation prepare, then commit K->>P: dispatch the fragments P-->>K: result, fidelity, latency end Note over K: assemble the result, record provenance C->>K: GET /api/v1/jobs/{id} K-->>C: COMPLETED with result ``` 1. **Submit.** The API stores a job as `QUEUED` and returns its id. The rest runs in the background. 2. **Plan.** The coordinator waits up to 10 seconds for service peers to appear, then compiles an execution plan. See [Circuit planning](/docs/concepts/circuit-planning). 3. **Reserve and run.** For each block in the plan, in order, the coordinator reserves a peer and sends it the fragments. See [Reservations](/docs/concepts/reservations). 4. **Assemble.** The coordinator builds the final result, records a provenance bundle and marks the job `COMPLETED`. See [Provenance](/docs/concepts/provenance). The five-layer picture on the [Introduction](/docs) groups these steps by responsibility. ## Where state lives [#where-state-lives] - **Job state** is stored as one run record per job, updated as the job moves through its states ([Jobs](/docs/concepts/jobs)). - **Reservations and executions** are event-sourced: every transition is an appended event, and the current state is rebuilt from the events. - **Peers** are held in the coordinator's in-memory registry, fed by discovery messages, with projections stored in MongoDB ([Peers and discovery](/docs/concepts/peers)). ## What this architecture does not give you [#what-this-architecture-does-not-give-you] There is one coordinator. QMesh does not run multiple coordinators or a consensus protocol between them, and the embedded workers used in local development share the coordinator's process. See [Scope](/docs/scope). # Circuit planning (/docs/concepts/circuit-planning) Before anything runs, the coordinator turns your circuit into an **execution plan**. This page describes what that plan contains and how peers are chosen. The plan for a finished job is available at `GET /api/v1/plans/{plan_id}`. ## 1. Normalize the circuit [#1-normalize-the-circuit] The parser accepts OpenQASM 2 and OpenQASM 3 style text. It detects the format from the header (`OPENQASM 2` or `OPENQASM 3`), or from the declarations (`qreg` for 2, `qubit[` for 3). It reads the qubit count from the register declaration and turns every operation into one entry. It rejects, with a clear error, a circuit that is empty, has no executable operations, has no qubit register, contains an operation it cannot read, or has an unterminated `for` block. Each operation is mapped to one of **eleven service types**: | Gate or keyword in the circuit | Service type | | --- | --- | | `h` | `hadamard` | | `cx`, `cnot` | `cnot` | | `cz` | `cz` | | `controlled_u` | `controlled_unitary` | | `qft` | `qft` | | `teleport`, `teleportation` | `teleportation` | | `bell_pair`, `bell` | `bell_pair` | | `syndrome_extraction` | `syndrome_extraction` | | `distillation` | `distillation` | | `measure`, `measurement_feedforward` | `measurement_feedforward` | | any other gate | `programmable_gate` | A peer offers a **service**, and each service type is a separate thing a peer can advertise. ## 2. Build the dependency graph [#2-build-the-dependency-graph] Two operations are dependent when the later one touches a qubit that the earlier one touched last. The planner records, for every operation, the previous operation on each of its qubits. Operations with no path between them can run in the same step. Each operation becomes one **fragment**. The planner does not yet cut a single gate into smaller pieces or search for better cut points: the unit of distribution is the operation. ## 3. Group fragments into stages and blocks [#3-group-fragments-into-stages-and-blocks] - A **stage** is a set of fragments whose dependencies are all in earlier stages. Stages run one after another. - Within a stage, fragments that act on connected qubits are merged into a **block**. A block is sent to a single peer and executed together, so entangled qubits stay on one machine. Block ids look like `block-s01-b01`: stage 1, block 1. ## 4. Choose a peer for each block [#4-choose-a-peer-for-each-block] For each block the planner considers only peers that are healthy, not stale, and advertise **every** service the block needs. It scores each candidate with a weighted cost: | Part | Weight | What it measures | | --- | --- | --- | | Failure risk | 55% | One minus the peer's fidelity for the services the block needs. | | Load | 25% | The peer's active reservations and executions, plus blocks already planned for it in this plan. | | Entanglement | 20% | A penalty when the candidate is not a peer that already holds the block's inputs, so state must move; it also grows with the number of separate qubit groups and the number of qubits in the state. | The lowest total cost wins and becomes the block's **primary** peer. The next three become its **fallbacks**. If no peer qualifies, planning stops with an error that names the block and the services it needed. When no embedded workers are configured and no peer has registered, the coordinator lists itself as the only candidate. In practice, run with workers (`QB2_LIBP2P_DEV_SERVICE_PEER_COUNT`) to see circuits spread out. ## What the plan stores [#what-the-plan-stores] The stored plan records each fragment, its dependencies, the stage and block it belongs to, its primary and fallback peers, and the score breakdown for every candidate. That makes it possible to see afterwards why a peer was chosen. The `plan_id` is generated for each run. ## Limits [#limits] Execution is Qiskit statevector simulation, so the number of qubits you can run is bounded by the memory needed for a statevector. Fidelity values describe how consistently the compiled circuit reproduces the ideal state in simulation. They are not measurements from hardware. # Jobs (/docs/concepts/jobs) Every circuit you submit becomes a **job**. A job is a stored record that the coordinator updates as the work advances, so you can poll it at any time. ## States [#states] ```mermaid stateDiagram-v2 [*] --> QUEUED QUEUED --> COMPILING COMPILING --> EXECUTING EXECUTING --> COMPLETED COMPILING --> FAILED EXECUTING --> FAILED COMPLETED --> [*] FAILED --> [*] ``` | State | Meaning | | --- | --- | | `QUEUED` | The job is stored and waiting for a background worker. This is the status the submit call returns. | | `COMPILING` | The coordinator is waiting for service peers, parsing the circuit and building the execution plan. | | `EXECUTING` | The plan exists and fragments are being run. `progress` reports how many have finished. | | `COMPLETED` | Every block ran. The result and the provenance bundle id are available. | | `FAILED` | Something went wrong. The `error` field says what. | `COMPLETED` and `FAILED` are terminal: the coordinator ignores a job that is already in either state. ## What a job record holds [#what-a-job-record-holds] `GET /api/v1/jobs/{job_id}` returns: | Field | Description | | --- | --- | | `job_id`, `status` | The identifier and the current state. | | `plan_id` | Set once the plan exists. Fetch it with `GET /api/v1/plans/{plan_id}`. | | `progress` | `total_fragments`, `completed_fragments`, `active_fragments`, `completion_ratio`, `finalizing`. `null` until the plan exists. | | `result` | `fragment_results` (one entry per fragment, including which peer ran it) and `quantum_result`. `null` until `COMPLETED`. | | `error` | The failure message when the job is `FAILED`, otherwise `null`. | | `circuit_text` | The circuit as submitted. | | `provenance_bundle_id` | The id to pass to the verification endpoint. | | `created_at`, `updated_at` | Timestamps. | `finalizing` is `true` when no fragment is still running but the job has not yet finished assembling the final result. ## Who can see a job [#who-can-see-a-job] A job belongs to the user who submitted it. Listing and reading return only your own jobs, unless your account has the admin role. A job you cannot see answers `404`, the same as one that does not exist. ## Failure [#failure] A job fails when any step raises: the circuit cannot be parsed, no peer offers a service the plan needs, or a block fails on every peer it was assigned (see [Reservations](/docs/concepts/reservations)). The `error` field carries the message, for example `Execution block block-s01-b01 failed across all assigned peers`, followed by the last peer error. Fragments that finished before the failure are kept on the stored record, but `result` stays `null`. ## Credits [#credits] Credit metering is **off by default**. It turns on when `QB2_CIRCUIT_JOB_CREDIT_COST` is greater than zero (the default is `0`). With it on, submitting a job **holds** that many credits first, and the hold is settled when the job ends: charged if the job `COMPLETED`, not charged otherwise. A submit with too few credits answers `402 Payment Required` and creates no job. Jobs paid through [x402](/docs/integrations/x402) are paid per request instead and bypass this meter. # Peers and discovery (/docs/concepts/peers) A **peer** is a libp2p node that offers one or more quantum services. The coordinator never has a fixed list of peers: it learns about them from messages they publish. ## Discovery [#discovery] Discovery uses libp2p GossipSub. Two topics are named after the rendezvous namespace (`QB2_LIBP2P_RENDEZVOUS_NAMESPACE`, default `quantum-backend`): | Topic | Carries | | --- | --- | | `quantum-backend.peer-advertisement.v1` | The peer's id, trust tier, network addresses, supported protocols, the services it offers and its position in its own log. | | `quantum-backend.peer-heartbeat.v1` | The peer's health status, active reservations and active executions. | The coordinator reads these messages and keeps a **registry** of peers in memory. It also stores projections of them in MongoDB and rebuilds the registry from those projections when it starts. ### Staleness [#staleness] A peer that has not sent an advertisement or heartbeat within `QB2_LIBP2P_STALE_PEER_TTL_SECONDS` (default 300 seconds) is **stale**. Stale peers are left out of listings and planning, but their entries are kept: when a stale peer advertises again, it is refreshed. Peers send heartbeats every `QB2_LIBP2P_HEARTBEAT_INTERVAL_SECONDS` (default 60). ## Ask the API [#ask-the-api] | Endpoint | Returns | | --- | --- | | `GET /api/v1/discovery/peers` | Known peers. Add `include_stale=true` to include stale ones. | | `GET /api/v1/discovery/peers/{peer_id}` | One peer in full, or `404`. | | `GET /api/v1/discovery/topology` | Every peer with its trust tier, health and whether it is stale, plus active and stale counts. | | `GET /api/v1/discovery/stats` | Network statistics. | | `GET /api/v1/services` | One entry per service per live peer, with fidelity, qubit range and availability. | All discovery routes require an authenticated user. ## Trust tiers [#trust-tiers] Every peer has a trust tier: | Tier | Meaning | | --- | --- | | `platform_managed` | Operated by the core team. | | `org_managed` | Operated by a verified organization. | | `user_contributed` | Contributed by an authenticated user. This is the default. | | `public_untrusted` | Unknown origin. Intended for sandboxed execution only. | | `quarantined` | Suspended pending review. | ## Enrollment [#enrollment] Enrollment records who owns a peer. `POST /api/v1/enrollment/peers` enrolls or updates a peer for the signed-in user. Only an `ADMIN` or `OPERATOR` can enroll a peer into `platform_managed` or `org_managed`. An administrator reviews a peer with `POST /api/v1/enrollment/peers/{peer_id}/action`, using the action `approve`, `reject` or `quarantine`, with an optional reason of up to 300 characters. ## Service quality [#service-quality] Each service has a quality record. When a block reports a fidelity score, the coordinator updates that peer's fidelity with it. The planner reads these values when it scores candidates. In local development the worker peers run inside the backend process. The repository includes an example script, `scripts/node-starter-template.py`, that starts an external node, but a managed network of third-party nodes is not finished. See [Scope](/docs/scope). # Provenance (/docs/concepts/provenance) When a circuit job completes, QMesh builds a **provenance bundle**: a record of what was submitted, which plan ran and what came back, committed with a hash. Anyone holding the bundle can recompute the hash and see whether anything changed. ## What a bundle proves [#what-a-bundle-proves] A bundle proves that a recorded result has **not changed** since it was recorded. It does not prove that the work ran on quantum hardware (it did not: see [Scope](/docs/scope)), that the answer is scientifically correct, that a peer was honest, or that stored data stays available. Treat an anchor receipt as evidence to check, not as a verification result. ## What goes in [#what-goes-in] The bundle commits to the input, the plan id, the ordered fragment results and the final result. It stores a hash of the result rather than a second copy of the potentially large statevector. It includes the run and owner ids and event records, and it does not include the circuit text or the counts. A bundle is created for **circuit jobs**. Finance jobs have their own provenance inside their results and are not covered by the bundle pipeline yet. If a new circuit run cannot persist its provenance, the run fails instead of reporting completion. ## How it is hashed [#how-it-is-hashed] 1. The bundle is turned into canonical JSON: keys sorted, no insignificant whitespace, UTF-8. This is a Python JSON profile, **not RFC 8785**, so another language must reproduce the same bytes rather than rely on its default serializer. 2. The commitment is the SHA-256 of an envelope `{"profile":"qmesh-provenance-sha256-v1","bundle":{…}}`. The `storage` and `anchors` fields are excluded, so attaching a storage location or an anchor later does not change the hash. 3. Many bundles can be combined under one **Merkle root**. Leaves are `SHA256(0x00 || digest)` and parents are `SHA256(0x01 || left || right)`. An odd last node at each level is promoted unchanged. This follows the tree shape of [RFC 6962](https://www.rfc-editor.org/rfc/rfc6962#section-2.1), and it is not compatible with OpenZeppelin's `MerkleProof`. ## Verify a run [#verify-a-run] ```bash curl http://localhost:8081/api/v1/provenance//verify ``` Only the run's owner or an administrator can verify; anything else is a `404`. The response lists checks, each of kind `hash`, `storage` or `anchor`, with one of four statuses: | Status | Meaning | | --- | --- | | `passed` | The stated check succeeded. | | `failed` | The evidence contradicts the recorded commitment. | | `unavailable` | An adapter, a historical commitment or an external service is missing. | | `not_applicable` | No attachment of that type was recorded. | The overall result is `unverified` when a check failed or none passed, `partially_verified` when some passed but the evidence is incomplete, and `verified` only when every applicable check passed **and** at least one storage or anchor check succeeded. Local database hashes alone never reach `verified`. ## Publish to IPFS [#publish-to-ipfs] If `KUBO_RPC_SECRET` is set, the coordinator publishes each completed bundle to the platform's Kubo node and pins it, after the run is already saved. Publishing is best effort: a failure never changes or fails the run, and it is not retried. Bundles up to 256 KiB are stored as a single block. Pinned content is announced on the **public** IPFS network. Bundles carry run and owner ids and hashes, not the circuit, but publish them only where that is acceptable. ## Anchor on a test network [#anchor-on-a-test-network] Anchoring writes the Merkle root of many bundles to Ethereum Sepolia, so anyone can later check that a bundle existed at that time. It arrived in [#113](https://github.com/QMeshPy/distributed-quantum/pull/113) (checking anchors) and [#114](https://github.com/QMeshPy/distributed-quantum/pull/114) (anchoring on a schedule). It is off until you configure it. - With `QB2_ANCHOR_NETWORK` and `QB2_ANCHOR_SENDER` set, the backend can **verify** anchors. - With `QB2_ANCHOR_PRIVATE_KEY` also set, it **anchors** circuit-job bundles on a schedule. Finance bundles are not anchored yet. Use a dedicated, low-balance key that is never a user, platform or deployer wallet. - An anchor is a zero-value transaction from the anchor account to itself. Its 45-byte calldata holds the marker `QMSHANC1`, the Merkle root, the batch size and a profile byte. No contract is deployed. - A run happens every `QB2_ANCHOR_INTERVAL_SECONDS` (default hourly). One batch is one transaction of about 22,000 gas, whatever its size (up to `QB2_ANCHOR_MAX_BATCH`, default 10,000). - A batch survives crashes: it moves `prepared`, `signed`, `submitted`, `anchored` (or `failed`), the signed transaction is stored **before** it is broadcast, a stuck transaction is replaced with a higher fee up to `QB2_ANCHOR_MAX_FEE_GWEI`, and anchors are recorded only after the block is finalized. Only one replica runs the scheduler at a time. - To rotate the key, set the new key and sender and list the old address in `QB2_ANCHOR_PAST_SENDERS`, so older anchors keep verifying. Only test networks are allowed. Anyone can check a downloaded bundle against its anchor without the database: ```bash cd backend uv run --frozen python scripts/verify_anchor.py bundle.json --sender 0xANCHOR_ACCOUNT ``` The script exits `0` if every anchor passed, `1` if any check failed, and `2` if none failed but a transaction was unavailable. # Reservations (/docs/concepts/reservations) A peer can only run so much at once, and it may refuse. So the coordinator never sends fragments blindly: it first **reserves** the peer, and only a committed reservation is followed by a dispatch. ## The protocol [#the-protocol] Reservations travel over libp2p streams. Their protocol ids are derived from the rendezvous namespace (default `quantum-backend`): | Step | Protocol id | | --- | --- | | Prepare | `/qb2/quantum-backend/reservation/prepare/1.0.0` | | Commit | `/qb2/quantum-backend/reservation/commit/1.0.0` | | Cancel | `/qb2/quantum-backend/reservation/cancel/1.0.0` | | Dispatch fragments | `/qb2/quantum-backend/execution/fragment-dispatch/1.0.0` | For each block, the coordinator sends a **prepare** request that states the service, the estimated number of qubits and the estimated circuit depth. The peer answers with its decision: accept, or decline with a reason. ## States [#states] ```mermaid stateDiagram-v2 [*] --> requested requested --> accepted requested --> rejected requested --> expired requested --> cancelled accepted --> committed accepted --> cancelled accepted --> expired ``` `committed`, `cancelled`, `expired` and `rejected` are terminal. A reservation has a time to live (`ttl_seconds`, 60 by default and at least 5). If it is not committed in time it can expire. The state of a reservation is not stored as a single value. Each transition is appended as an event, and the current state is rebuilt from the events. `GET /api/v1/reservations/{reservation_id}` returns that rebuilt state. ## What the coordinator does for one block [#what-the-coordinator-does-for-one-block] 1. Pick the next candidate peer: the primary first, then up to three fallbacks, in the order the [plan](/docs/concepts/circuit-planning) ranked them. 2. Send **prepare**. If the peer does not accept, record a rejection and move to the next candidate. 3. Send **commit**. If it does not return `committed`, send **cancel**, record the cancellation and move on. 4. Send the fragments with **dispatch**. 5. If the peer reports a failed execution, record the failure and move to the next candidate. 6. If the peer completes, record its fidelity and latency and continue to the next block. When every candidate has failed, the block fails and so does the job: `Execution block failed across all assigned peers`. Each fragment result records the number of `attempts` it took. ## API [#api] The coordinator drives reservations itself during a job. The endpoints below are for inspection and tooling. | Endpoint | Purpose | | --- | --- | | `POST /api/v1/reservations` | Request a reservation. Takes `workflow_run_id`, `fragment_id`, `service_id`, optional `requesting_peer_id`, `ttl_seconds` and an `idempotency_key`. | | `GET /api/v1/reservations/{reservation_id}` | Read the current state. | | `POST /api/v1/reservations/{reservation_id}/cancel` | Cancel a reservation. | An `idempotency_key` makes a repeated request safe to retry: a request with a key that was already used returns the existing reservation instead of creating another. # Introduction (/docs) QMesh is an open-source research platform that treats quantum operations as **network services**. You submit an [OpenQASM](https://openqasm.com) circuit to a coordinator. The coordinator discovers worker peers over libp2p, splits the circuit into fragments, reserves capacity on the peers, runs the fragments, and returns the result together with a provenance record you can verify. QMesh runs circuits on Qiskit statevector **simulation**. It does not control quantum hardware. The network, the job lifecycle, the payment flow and the provenance records are real software, and every blockchain it uses is a **testnet**. See [Scope](/docs/scope) for the full picture. ## What you can do with it [#what-you-can-do-with-it] - **Run a circuit across peers.** Submit OpenQASM over HTTP and watch the job move from `QUEUED` to `COMPLETED`. See [Basic usage](/docs/basic-usage). - **Optimize a portfolio.** Upload asset data and compare a QAOA run with classical baselines. - **Price options and measure risk.** Run option-pricing and risk jobs (VaR, CVaR, credit risk) through the same job model. - **Pay per request.** Expose a paid endpoint with the [x402](/docs/integrations/x402) `402 Payment Required` flow, settled in USDC on Algorand TestNet, or sell [credit packs](/docs/integrations/billing) through Stripe. - **Verify a result.** Every completed circuit job gets a provenance bundle with a Merkle root, which can be pinned to IPFS and anchored on Ethereum Sepolia. See [Provenance](/docs/concepts/provenance). - **Use real inputs.** Optimize portfolios from [Chainlink](/docs/integrations/chainlink) price history. - **Give agents a budget.** Cap what an AI agent can spend with [Starknet session keys](/docs/integrations/starknet), and choose [private, attested inference](/docs/integrations/agent-llm). - **Share content.** Publish circuits and runs to [VAULT](/docs/integrations/vault-ipfs) and open any CID with verification. See every integration, and its state, in the [overview](/docs/integrations/overview). ## How a request flows [#how-a-request-flows] A request passes through five layers. Each one has its own page under Concepts. ### Your app [#your-app] Submit an OpenQASM circuit to the REST API, or paste it into the operator console. ### Coordinator [#coordinator] Normalize the circuit, build an execution plan and split it into fragments. See [Circuit planning](/docs/concepts/circuit-planning). ### Payments and registry [#payments-and-registry] Charge for the request, either from prepaid credits or through an x402 payment, and look up the services that are available. ### Peer mesh [#peer-mesh] Reserve capacity on each peer, then stream the fragments to them over libp2p. See [Peers and discovery](/docs/concepts/peers) and [Reservations](/docs/concepts/reservations). ### Provenance [#provenance] Hash the inputs, the plan and the fragment results into a bundle with a Merkle root. ## Where to go next [#where-to-go-next] } title="Installation" href="/docs/installation"> Run the backend and the console on your machine. } title="Basic usage" href="/docs/basic-usage"> Submit a circuit, poll the job and read the result. } title="Architecture" href="/docs/concepts/architecture"> The pieces of the system and how they fit. } title="Integrations" href="/docs/integrations/overview"> Payments, chains, storage and agents, and where each one stands. ## AI resources [#ai-resources] Every page is available as Markdown: add `.md` to its URL, or use **Copy Markdown** at the top of the page. The whole site is indexed at [`/llms.txt`](/llms.txt) and [`/llms-full.txt`](/llms-full.txt). # Installation (/docs/installation) This page gets the backend running locally with its embedded worker peers, then starts the console. It uses the `.env.example` defaults, which turn authentication **off**. That is right for your own machine and wrong for anything reachable from the internet. ### Install the prerequisites [#install-the-prerequisites] - **Python 3.12 or 3.13** (the backend declares `>=3.12,<3.14`) and [uv](https://docs.astral.sh/uv/). - **MongoDB**, local or [Atlas](https://www.mongodb.com/atlas). It is the backend's only database. - **Node.js 22** and [Bun](https://bun.sh), for the console. - **Docker**, only if you want the full stack or the content node. Clone the product repository: ```bash git clone https://github.com/QMeshPy/distributed-quantum.git cd distributed-quantum ``` ### Configure the backend [#configure-the-backend] ```bash cd backend cp .env.example .env ``` The defaults in `.env.example` that matter for a first run: ```txt title="backend/.env" QB2_AUTH_REQUIRED=false QB2_MONGODB_TARGET=local QB2_MONGODB_LOCAL_URI=mongodb://127.0.0.1:27017 QB2_MONGODB_DATABASE=qds QB2_LIBP2P_DEV_SERVICE_PEER_COUNT=4 ``` `QB2_LIBP2P_DEV_SERVICE_PEER_COUNT=4` starts four worker peers inside the backend process. The code default is `0`, which starts none, so fragments would not spread across peers. To use an Atlas cluster, set `QB2_MONGODB_TARGET=remote` and `QB2_MONGODB_REMOTE_URI`. See [Configuration](/docs/reference/configuration). ### Start the backend [#start-the-backend] ```bash make install make run ``` `make run` frees its ports first: it **kills any process** listening on the API port (`8081` by default) and the libp2p port (`4011` by default). Do not run it while something else you care about uses them. `make run-clean` additionally deletes the local peerstore and peer logs before starting. Check that it is up: ```bash curl http://localhost:8081/api/v1/health ``` The response includes `"status": "ok"` and the state of the persistence layer. Interactive API docs are served at [`http://localhost:8081/docs`](http://localhost:8081/docs). ### Start the console [#start-the-console] ```bash cd ../frontend cp .env.example .env.local bun install bun run dev ``` In `.env.local`, `NEXT_PUBLIC_BACKEND_URL` must point at the backend (`http://localhost:8081`), and `NEXT_PUBLIC_TRIAL_DISABLED=true` skips the one-hour trial gate locally. Open [`http://localhost:3000`](http://localhost:3000). The console's sign-in uses email codes sent through Resend, plus Sign-in with Ethereum. Those need `MONGODB_URI`, `BETTER_AUTH_SECRET`, `RESEND_API_KEY` and `OTP_EMAIL_FROM`; all are listed in `frontend/.env.example`. You do not need the console to use the API: [Basic usage](/docs/basic-usage) calls the backend directly. ## Run the full stack with Docker [#run-the-full-stack-with-docker] The repository's `docker-compose.yaml` defines the backend, a Caddy reverse proxy and a Kubo content node (plus an optional Prometheus under the `monitoring` profile). It does **not** include the console, which is deployed separately. ```bash cp .env.example .env docker compose up --build ``` The root `.env.example` is written for a hosted MongoDB (Atlas); set your MongoDB connection in it before you start. The Kubo node needs `KUBO_RPC_SECRET`, a private random token of 32 to 128 characters, or it will refuse to start. ## Verify the install [#verify-the-install] If `make run` is up and four peers joined, listing the services returns entries: ```bash curl http://localhost:8081/api/v1/services ``` An empty list means no peers have registered yet. Wait a few seconds and retry, then check that `QB2_LIBP2P_DEV_SERVICE_PEER_COUNT` is set in `backend/.env`. # Agent LLM providers (/docs/integrations/agent-llm) Agent chat in the console talks to a language model. QMesh lets you pick the provider, and with **NEAR AI Cloud** it checks cryptographically that the reply came from a model running in a trusted execution environment (TEE). The work landed in three pull requests: [#81](https://github.com/QMeshPy/distributed-quantum/pull/81) (Groq and OpenRouter), [#91](https://github.com/QMeshPy/distributed-quantum/pull/91) (NEAR AI Cloud) and [#101](https://github.com/QMeshPy/distributed-quantum/pull/101) (the provider dropdown). [#81](https://github.com/QMeshPy/distributed-quantum/pull/81) **removed AWS Bedrock** from agent chat and replaced it with Groq and OpenRouter. Bedrock is no longer an agent option. ## The providers [#the-providers] | Provider | Value | Model | Notes | | --- | --- | --- | --- | | Groq | `groq` | `GROQ_MODEL`, default `openai/gpt-oss-20b` | Needs `GROQ_API_KEY`. | | OpenRouter | `openrouter` | `OPENROUTER_MODEL` | **Free models only.** QMesh sends zero-price provider limits, so it cannot spend money. Needs `OPENROUTER_API_KEY`. | | NEAR AI Cloud | `near` | `NEAR_MODEL`, for example `z-ai/glm-5.3-flash` | Must be a Confidential TEE model with attestation support. Needs `NEARAI_API_KEY`. | `AGENT_LLM_PROVIDER` sets the server default. The chat page also has a **provider dropdown** that overrides it per browser and remembers the choice. A provider that is not configured is shown disabled, with "(not configured)". There is no paid fallback and no automatic retry on a rate limit: free models come and go. `GET /api/v1/agents/llm-providers` lists the providers and whether each is configured. ## NEAR AI Cloud private inference [#near-ai-cloud-private-inference] With `AGENT_LLM_PROVIDER=near`, QMesh uses `nearai-inference-sdk` against `cloud-api.near.ai`. The guarantees: - **Every tool round is attested.** The agent loop is capped at 8 rounds, and each round makes its completions and then checks a signature before the next tool runs. - **It fails closed.** A non-TEE model, a missing key, or an HTTP 402 or 404 stops the request. There is **no silent fallback** to Groq or OpenRouter. The API answers `503`, and the message says that no fallback provider was used. - **The result is shown.** Each message carries a badge: **Model TEE verified**, **Gateway verified** or **Unverified**. A NEAR reply is slower than the others because of those attestation round trips. Set a spend limit for the key in the NEAR dashboard. ```txt title="backend/.env" AGENT_LLM_PROVIDER=near NEARAI_API_KEY= NEAR_MODEL=z-ai/glm-5.3-flash ``` Pick the model id from `https://cloud-api.near.ai/v1/model/list`, choosing one with `attestationSupported=true`. ## Related agent pieces [#related-agent-pieces] - **Wallets.** Agents use Coinbase CDP server wallets on Base Sepolia. The API key is the secret key JSON from CDP: `id` goes in `CDP_API_KEY_ID` and `privateKey` in `CDP_API_KEY_SECRET`. `CDP_WALLET_SECRET` is a separate one-time secret from the CDP portal. Creating a wallet requests test ETH and USDC from the CDP faucet. - **Spending limits on-chain.** See [Starknet session keys](/docs/integrations/starknet). - **Storing a report.** See [Toju agent storage](/docs/integrations/toju). The agent chat is experimental. Keys for every provider are server-side only: never prefix them with `NEXT_PUBLIC_`. # Billing (/docs/integrations/billing) Billing is **optional** and runs in Stripe **test mode**. With nothing configured the console works exactly as it does without it. Three pieces build on each other: a subscription that lifts the free-trial gate, credit packs that pay for circuit runs, and an option to buy those packs in USDC. Worker payouts are a separate effort (see the end of this page). | Piece | Pull request | Switched on by | | --- | --- | --- | | Subscriptions | [#42](https://github.com/QMeshPy/distributed-quantum/pull/42) | `STRIPE_SECRET_KEY`, `STRIPE_PRICE_ID` and `STRIPE_WEBHOOK_SECRET` | | Credit packs and metered runs | [#84](https://github.com/QMeshPy/distributed-quantum/pull/84) | Subscriptions, plus `STRIPE_CREDIT_PACKS` and the billing secret | | USDC credit packs | [#88](https://github.com/QMeshPy/distributed-quantum/pull/88) | `STRIPE_STABLECOIN_PAYMENTS=true` | ## Subscriptions [#subscriptions] Users on the free trial can upgrade with Stripe Checkout. Subscribers manage or cancel their plan in the Stripe customer portal. An active subscription sets `hasSubscription`, which removes the trial gate. The integration uses the Better Auth Stripe plugin (`@better-auth/stripe`) with a single `pro` plan. In the console, **Settings → Billing** shows the plan status with **Upgrade** and **Manage** buttons. The webhook lives at `/api/auth/stripe/webhook`: - It verifies Stripe's signature before it does anything else. - It is idempotent on the event id. Claimed ids are stored in `stripe_webhook_events` with a unique index, so a redelivered event is acknowledged and not processed twice. - If handling fails it releases its claim, so Stripe's retry is processed. - It recomputes `hasSubscription` by fetching the customer's subscriptions from Stripe instead of trusting the event payload, and it counts `active` and `trialing` as subscribed. No card data touches QMesh. Payment details are entered only on Stripe-hosted Checkout and the customer portal. ```txt title="frontend/.env.local" STRIPE_SECRET_KEY=sk_test_... STRIPE_PRICE_ID=price_... STRIPE_WEBHOOK_SECRET=whsec_... ``` To test locally, run `stripe listen --forward-to localhost:3000/api/auth/stripe/webhook`, put the `whsec_...` it prints in `STRIPE_WEBHOOK_SECRET`, and pay with the test card `4242 4242 4242 4242`. ## Credit packs [#credit-packs] A **credit** is one USDC. A user buys a one-off pack through Stripe Checkout, the purchase is written to the [credit ledger](/docs/integrations/credits-ledger) exactly once, and circuit runs spend credits. ```txt title="frontend/.env.local" # One-time Stripe Prices and the credits each grants STRIPE_CREDIT_PACKS=price_abc:10,price_def:50 BILLING_INTERNAL_SECRET= ``` ```txt title="backend/.env" QB2_BILLING_INTERNAL_SECRET= QB2_CIRCUIT_JOB_CREDIT_COST=1 ``` How a purchase becomes credits: 1. `POST /api/billing/credits/checkout` starts a payment-mode Checkout Session for a configured pack. 2. The Stripe webhook receives `checkout.session.completed`. Credits are worked out from **Stripe's own line items**, never from anything the browser sent. A purchase paid later (a delayed method) is credited on `checkout.session.async_payment_succeeded`. 3. The console calls the backend's internal route `POST /api/v1/internal/billing/credit-purchases`, protected by the shared secret. The route exists only when `QB2_BILLING_INTERNAL_SECRET` is set. 4. The purchase is keyed `stripe:checkout:`, so a redelivered webhook, or the completed and async-succeeded events of one session, credit once. A redelivery also finishes a credit that a crash interrupted. `GET /api/v1/credits/balance` returns the caller's balance and the price of a circuit run. ### Metered circuit runs [#metered-circuit-runs] Runs stay free until `QB2_CIRCUIT_JOB_CREDIT_COST` is above `0`. With it above zero: - Submitting a job **holds** the cost. With too few credits the API answers `402` with the error code `insufficient_credits`. - A job that **completes** captures the hold. A job that **fails** releases it. - Jobs paid through [x402](/docs/integrations/x402) are paid per request and skip the meter. ## USDC credit packs [#usdc-credit-packs] With `STRIPE_STABLECOIN_PAYMENTS=true`, USD-priced packs can also be bought in USDC (and other stablecoins Stripe supports) through Stripe's `crypto` payment method. Stripe settles to QMesh's balance in USD, so QMesh holds no wallet keys and runs no on-chain code for this. The purchase is recorded on its own `stablecoin` rail in the ledger, with the same exactly-once guarantee. Limits that come from Stripe, not QMesh: - The Stripe account must be approved for **Stablecoins and Cryptocurrency**, which is limited by region. - USD prices only, and up to US$10,000 per transaction. - Stablecoin payments cannot be disputed. Refunds are supported and go back to the paying wallet. A refund does **not** yet debit the credits it granted. The same is true for card refunds. ## In review: paying workers [#in-review-paying-workers] These are not merged. Treat them as plans. - [#104](https://github.com/QMeshPy/distributed-quantum/pull/104) pays workers **per fragment** in USDC as results are recorded, capped at the job's credit hold, with each fragment paid once. - [#122](https://github.com/QMeshPy/distributed-quantum/pull/122) adds optional **Stripe Connect Express** onboarding so a worker can cash out to a bank or a stablecoin wallet. It is gated by `STRIPE_CONNECT_ENABLED`. - The decision behind both is recorded in a [spike](https://github.com/QMeshPy/distributed-quantum/issues/77): on-chain USDC is the default, and Connect is an optional off-ramp for workers who want a normal cash-out. # Chain registry (/docs/integrations/chain-registry) QMesh touches several networks, and its code should not scatter chain names, explorer links or RPC addresses through services. The **chain registry** holds them in one reviewed file, and every other part asks it. It grew over four pull requests: [#39](https://github.com/QMeshPy/distributed-quantum/pull/39) (the registry), [#92](https://github.com/QMeshPy/distributed-quantum/pull/92) (RPC endpoints and a read-only adapter), [#93](https://github.com/QMeshPy/distributed-quantum/pull/93) (shared wallet services) and [#102](https://github.com/QMeshPy/distributed-quantum/pull/102) (the public API and the console's chain selector). ## The networks [#the-networks] The registry ships as `registry.toml` inside the backend package. Networks are named by a [CAIP-2](https://chainagnostic.org/CAIPs/caip-2) id. | Network | Id | Alias | What QMesh may do there | | --- | --- | --- | --- | | Base Sepolia (default) | `eip155:84532` | `base-sepolia` | Wallets, balances, USDC transfers, Aave supply and withdraw, a faucet hint, explorer links, transaction checks | | Ethereum Sepolia | `eip155:11155111` | `ethereum-sepolia` | Explorer links, transaction checks, the ETH/USD price feed, provenance anchoring | | Robinhood Chain Testnet | `eip155:46630` | `robinhood-testnet` | Explorer links and transaction checks only | | Starknet Sepolia | `starknet:SN_SEPOLIA` | `starknet-sepolia` | Explorer links and transaction checks, plus the opt-in [session policy](/docs/integrations/starknet) | **Mainnet entries must have `enabled = false`.** A disabled entry cannot be used for any operation and is left out of the public list. The only mainnet access is the read-only market-data exception described in [Chainlink](/docs/integrations/chainlink). ## Capabilities [#capabilities] Each entry lists the operations it supports, such as `create_wallet`, `get_balance`, `transfer_stablecoin`, `verify_transaction`, `get_price` and `anchor_root`. A service checks the capability **before** it calls a provider or an RPC. That is how Robinhood Chain can be in the registry for explorer links without any code being able to send a transaction there. Asking for a name that is empty, unknown or disabled is an error. QMesh never silently falls back to Base Sepolia, and there is no global "active" network. ## RPC endpoints without leaking keys [#rpc-endpoints-without-leaking-keys] An entry names an environment variable, never a URL, plus an optional keyless public fallback: | Network | Override variable | Public fallback | | --- | --- | --- | | Base Sepolia | `QB2_BASE_SEPOLIA_RPC_URL` | `https://sepolia.base.org` | | Ethereum Sepolia | `QB2_ETHEREUM_SEPOLIA_RPC_URL` | `https://ethereum-sepolia-rpc.publicnode.com` | | Robinhood Chain Testnet | `QB2_ROBINHOOD_TESTNET_RPC_URL` | `https://rpc.testnet.chain.robinhood.com` | | Starknet Sepolia | `QB2_STARKNET_SEPOLIA_RPC_URL` | `https://starknet-sepolia-rpc.publicnode.com` | An override must be HTTPS, or plain HTTP to `localhost` for a local node. It may carry an API key. The URL is **never** returned by the API or logged: failures are logged by exception type only. ## The read-only adapter [#the-read-only-adapter] `EvmRpcAdapter` reads from a network and holds no keys: - `get_balance(address)` returns native and stablecoin balances using the registry's token address and decimals. - `verify_transaction(tx_hash, ...)` answers `not_found`, `pending`, `failed` or `confirmed`, with a confirmation count, and can check for an exact stablecoin transfer between two addresses. - Before its first query it compares the RPC's chain id with the registry's, so a mistyped override cannot read another network. Signing still goes through the wallet provider (Coinbase CDP). No adapter holds a key. ## The public endpoint [#the-public-endpoint] `GET /api/v1/chains` lists the enabled networks and the operations each supports, including aliases. It never returns RPC URLs or escrow configuration. The console uses it for its chain selector, and the wallet page takes network names and explorer links from it. A network that lacks the capability an action needs is listed as "(not available)". ## Configure [#configure] | Variable | Purpose | | --- | --- | | `QB2_CHAIN_REGISTRY_PATH` | Use another registry file. Relative paths use the working directory. | | `NETWORK` | The wallet network, by alias or id. Unset means the registry default. An unknown value stops startup. | Wallets are single-network for now. A second wallet network, a network per request and per-payment network storage wait for a second chain that supports wallets. # Chainlink and tokenized stocks (/docs/integrations/chainlink) Finance and risk jobs need prices, and a price you cannot check is a weak input. QMesh reads **Chainlink Data Feeds** over JSON-RPC, validates every round, and records the exact rounds it used in the job's provenance so the inputs can be re-read later. There are two uses: an ETH/USD feed on Ethereum Sepolia, and price history for **Robinhood tokenized stocks**. ## ETH/USD on Ethereum Sepolia [#ethusd-on-ethereum-sepolia] [#98](https://github.com/QMeshPy/distributed-quantum/pull/98) adds optional Chainlink ETH/USD pricing to European call pricing, risk analysis and portfolio valuation. - It validates feed freshness, price deviation and block consistency. - It keeps historical inputs for risk and portfolio calculations. - It records oracle observations and reproducible inputs with verifiable hashes. - Provenance files are downloadable with authentication, and the console signs its finance requests. - Only Ethereum Sepolia ETH/USD is supported. An unsupported portfolio asset is marked as such. **No price is substituted.** ## Robinhood tokenized stocks [#robinhood-tokenized-stocks] Three pull requests ([#106](https://github.com/QMeshPy/distributed-quantum/pull/106), [#107](https://github.com/QMeshPy/distributed-quantum/pull/107) and [#108](https://github.com/QMeshPy/distributed-quantum/pull/108)) let you optimize a portfolio of stock tokens from the console's finance page. Robinhood's Stock Tokens and their Chainlink feeds exist on Robinhood Chain **mainnet** only. The registry keeps only the testnet. Price history is read from mainnet with `eth_call` alone: no wallet, no signature, no transaction. ### The nine assets [#the-nine-assets] AAPL, MSFT, NVDA, GOOGL, AMZN, META, TSLA, SPY and QQQ. The list is a reviewed allowlist that joins Robinhood's asset list with Chainlink's feed directory. Each entry was checked on-chain: the token's symbol, decimals and multiplier, and the feed's description and decimals. ### The data rules [#the-data-rules] | Rule | Detail | | --- | --- | | Pricing | Token prices. The feeds already include the token multiplier, which is never applied again. | | Sampling | Each weekday's price is the last valid round at or before **21:00 UTC** (after the US close), if it is at most 36 hours old. | | Gaps | A weekday on which no selected feed published (a market holiday) is dropped. Nothing is zero-filled. | | Launch rounds | Each feed's first rounds were published at about 100 million times the price, and are excluded. | | Range | At least 60 weekdays and at most 366 days, with no future end date. | | Fail closed | A bad answer, a move above 50% between rounds, feed drift, a feed silent for more than 7 days, or fewer than 60 aligned trading days rejects the history. | ### Use it [#use-it] ```bash curl http://localhost:8081/api/v1/finance/tokenized-assets ``` ```bash curl -X POST http://localhost:8081/api/v1/finance/tokenized-portfolio \ -H "Content-Type: application/json" \ -d '{"symbols": ["AAPL", "MSFT", "SPY"], "start_date": "2026-06-23", "end_date": "2026-10-28"}' ``` The request is validated first: an unsupported or duplicate asset, a bad or future date, a range under 60 weekdays or an unacceptable `budget` is answered with `422` before any job exists. A valid request queues a normal finance job. When it completes, `GET /api/v1/finance/{job_id}` adds: - `market_data`: the source, chain, block, aligned date range, and per-asset token and feed addresses with first and last round ids. - `allocation_weights`: the optimizer's equal weights over each method's selected assets. - `disclaimer`: analysis only, not investment advice. Nothing is signed or traded. - `provenance`: every daily close as a Chainlink observation, so the inputs can be re-read by round id. Insufficient or invalid history fails the job with a fixed message. At most two history reads run at once per process, because the public RPC is rate-limited. Set `QB2_ROBINHOOD_MAINNET_RPC_URL` to use your own. [#115](https://github.com/QMeshPy/distributed-quantum/pull/115) later compacted the provenance (removing duplicated observations) and made the backend serve provenance downloads byte for byte so hashes still match. Stock Tokens are not offered to U.S. persons or in restricted jurisdictions, and QMesh only analyzes them. It never holds, trades or recommends them. ## In review [#in-review] [#123](https://github.com/QMeshPy/distributed-quantum/pull/123) adds a read-only **wallet portfolio** analyzer: `POST /api/v1/risk/analyze-wallet` prices an address's holdings (or an ENS name) and runs the same VaR and CVaR engine. A holding that cannot be priced is listed, not dropped. It is open and may change. # Credits and ledger (/docs/integrations/credits-ledger) Every payment rail (x402, cards, stablecoins, on-chain transfers) records the same kind of **payment event**, and value that becomes credits is written to a single **append-only, USDC-denominated ledger**. The goal is "accept anywhere, settle once": idempotency, reconciliation and double-payment protection exist once, not once per rail. It was added in [#79](https://github.com/QMeshPy/distributed-quantum/pull/79). ## Four collections [#four-collections] | Collection | Holds | Unique on | | --- | --- | --- | | `payment_events` | One document per payment, with its status history | `idempotency_key` | | `credit_ledger` | Append-only entries: `credit`, `hold`, `capture`, `release` | `idempotency_key` | | `credit_holds` | A guard: how much of a hold can still be captured or released | `hold_id` | | `credit_balances` | A guard: cached `available` and `held` per account | `account_id` | Money is stored as `Decimal128`, never as a float. ## Payment events [#payment-events] Replaying an idempotency key returns the stored event. Replaying it with a different rail, purpose, asset, amount or account is an error. An event moves through these states: ```mermaid stateDiagram-v2 [*] --> pending pending --> confirmed pending --> failed pending --> uncertain uncertain --> confirmed: admin reconcile uncertain --> failed: admin reconcile confirmed --> refunded ``` - **Confirming** an event credits its ledger account, and confirming twice is harmless: it finishes a credit a crash interrupted. - **Uncertain** is for a payment whose outcome is unknown, for example a timeout after a transaction may have been broadcast. An uncertain payment is **never retried automatically**. An administrator resolves it after checking the rail. - Keys name the payment itself, so a retry makes the same key: `stripe:checkout:` for a credit pack, or `fragment_payout:::` for a payout. ## The credit ledger [#the-credit-ledger] Entries are never updated or deleted. A balance is derived by adding them up: a credit adds to `available`, a **hold** moves value from `available` to `held`, a **capture** spends held value, and a **release** gives it back. ```text submit a job -> hold the cost job completes -> capture it job fails -> release it ``` MongoDB here is a standalone server without multi-document transactions. So each hold, capture or release changes its guard document with one atomic conditional update (a hold succeeds only if `available >= amount`), and the entry's unique key decides between concurrent replays. Writes are ordered so a crash **fails closed**: value can end up reserved until reconciled, but it is never spendable twice. ## Reconciliation [#reconciliation] A sweep runs every `QB2_PAYMENT_RECONCILE_INTERVAL_SECONDS` (default `300`, `0` disables it) and on demand. It marks pending events older than 30 minutes that have no external reference as uncertain, and it compares each account's cached balance with the sum of its ledger entries and **reports** any drift. It does not repair drift, so an operator can find the cause first. ## Admin API [#admin-api] All routes need the admin role, and none can write ledger entries. | Method and path | Purpose | | --- | --- | | `GET /api/v1/admin/payments/events` | List events. Filters: `status`, `job_id`, `proposal_id`, `limit` | | `GET /api/v1/admin/payments/events/{key}` | One event with its status history | | `POST /api/v1/admin/payments/events/{key}/reconcile` | Resolve a pending or uncertain event as `confirmed` or `failed`, with a reason | | `POST /api/v1/admin/payments/reconciliation` | Run the sweep now | | `GET /api/v1/admin/payments/accounts/{account_id}/balance` | A balance derived from the ledger | Users read their own balance at `GET /api/v1/credits/balance`. See [Billing](/docs/integrations/billing) for how credits are bought and spent. # ENS (/docs/integrations/ens) [ENS](https://ens.domains) maps human-readable names such as `alice.eth` to addresses. QMesh uses it so that people can type and read names instead of hex strings. It arrived in [#97](https://github.com/QMeshPy/distributed-quantum/pull/97), which supersedes an earlier attempt. ## What it does [#what-it-does] - **Forward and reverse resolution.** A backend service, an HTTP API and console hooks resolve a name to an address and an address to its verified primary name. Results are cached for 5 minutes by default (never more than 15) with an 8-second timeout, and the console does a fresh lookup right before a send. - **Names in the wallet.** The transfer form accepts `name.eth`, shows the resolved, checksummed address, and requires an explicit confirmation. Only the **address** is stored, never the name. - **Names on screen.** Proposal funders, the researcher wallet and the wallet strip show the primary ENS name, with the address as a fallback. - **No guessing.** If resolution fails, the console shows an error. It never falls back to a guessed address. ## API [#api] | Method and path | Purpose | | --- | --- | | `GET /api/v1/ens/resolve?name=alice.eth` | Name to address. Needs an RPC for the configured network. Add `fresh=true` to skip the cache. | | `GET /api/v1/ens/reverse?address=0x...` | Address to its verified primary name, or `null` if none is set. | | `GET /api/v1/ens/workers` | The worker-subname proof of concept (list). | | `GET /api/v1/ens/workers/{subname}` | One worker record in the documented proof-of-concept shape. | ## Configure [#configure] | Variable | Purpose | | --- | --- | | `ENS_NETWORK` | `sepolia` or `mainnet`. By default it follows the payment network. | | `ENS_RPC_URL` | Override the default public RPC for that network. | ## Worker subnames: a proof of concept [#worker-subnames-a-proof-of-concept] The idea is for each worker to have a name under a QMesh-controlled domain (for example `worker-alpha`), with the name's records describing the worker. The pull request ships a schema document and a stub endpoint that returns that shape. It does **not** register names on-chain. Storing a `contenthash` record is deferred to a later phase. ## Sign-in names [#sign-in-names] [Sign-in with Ethereum](/docs/integrations/siwe) uses reverse resolution to fill in a user's display name and avatar. # Kubo content node (/docs/integrations/kubo) The Docker Compose stack includes a **Kubo** node, the reference IPFS implementation, pinned to **v0.43.1** by release tag and image digest. It gives QMesh a content store it controls: provenance bundles are published to it, and the backend can read content back with the CID verified. It arrived over several pull requests: [#80](https://github.com/QMeshPy/distributed-quantum/pull/80) (the node), [#82](https://github.com/QMeshPy/distributed-quantum/pull/82) (a locked-down RPC and a read-only gateway), [#85](https://github.com/QMeshPy/distributed-quantum/pull/85) to [#87](https://github.com/QMeshPy/distributed-quantum/pull/87) (a typed client), [#94](https://github.com/QMeshPy/distributed-quantum/pull/94) (CID-verified gateway reads), [#95](https://github.com/QMeshPy/distributed-quantum/pull/95) (publishing provenance) and [#96](https://github.com/QMeshPy/distributed-quantum/pull/96) (storage limits, garbage collection and metrics). IPFS is public, content-addressed networking. It is **not** confidential storage. Never add private data expecting its CID to be secret. ## Start it [#start-it] Set a private RPC token first. It is 32 to 128 characters of letters, digits, `_` or `-`, with no `bearer:` prefix. Without a valid secret the node refuses to start (compute is unaffected). ```bash openssl rand -hex 32 # put the result in .env as KUBO_RPC_SECRET chmod 600 .env docker compose up -d --wait kubo docker compose exec kubo sh /opt/qmesh/kubo-rpc.sh diag healthy ``` ## What is exposed [#what-is-exposed] | Interface | Exposure | | --- | --- | | Swarm `4001` (TCP and UDP) | Published, for IPFS peers. Separate from the quantum workers' libp2p. | | Gateway `8080` | `127.0.0.1` only (`KUBO_GATEWAY_PORT` changes the host port). Reads `/ipfs/`. No writes. | | RPC `5001` | **Not published** and not proxied by Caddy. Only the backend reaches it, on the private `content_network`. | The RPC needs `Authorization: Bearer `. The backend's scope allows only `id`, `diag/healthy`, `add`, `cat`, `pin/add`, `pin/rm`, `pin/ls`, `dag/import` and `block/stat`. Configuration, shutdown and other admin commands are denied even with the token. A start-up hook reapplies these rules on **every** start, so manual edits to managed keys are overwritten. To rotate the token, change `.env` and run `docker compose up -d --force-recreate kubo backend`. Pins and identity are kept. ## Storage and garbage collection [#storage-and-garbage-collection] | Variable | Default | Meaning | | --- | --- | --- | | `KUBO_STORAGE_MAX` | `10GB` | A **soft** target for garbage collection, not a disk quota. | | `KUBO_GC_PERIOD` | `1h` | How often the daemon checks. | | (fixed) | 90% | The watermark that triggers collection. | Garbage collection only removes **unpinned, unreferenced** blocks. Pinned data is kept even above the target, so pins can fill the disk. Size the volume with headroom and watch it. Upgrading turns automatic collection on for existing volumes: pin what you need and back up the volume first. Never `docker compose down --volumes` on a deployment you want to keep. ## Metrics [#metrics] ```bash docker compose --profile monitoring up -d --wait kubo prometheus ``` A pinned Prometheus scrapes Kubo's native metrics every 30 seconds and keeps about 7 days or 512 MB. Neither the Prometheus UI nor Kubo's metrics has a published port. No Alertmanager is installed. Watch the disk that holds `kubo_data` yourself: the exporter does not report free space. ## How the backend uses it [#how-the-backend-uses-it] - `KuboClient` is an async client for `add`, `pin_add`, `pin_rm`, `pin_ls`, `dag_import` (CAR files) and `block_stat`, with bounded requests and safe errors that never include remote error text. **It does not retry**: a failed or timed-out write may already have stored blocks, so reconcile instead of replaying. - A gateway client fetches by CID and **verifies the bytes** against it. - When `KUBO_RPC_SECRET` is set, each completed circuit run's [provenance bundle](/docs/concepts/provenance) is published and pinned, best effort. A failure never changes the run. Content failures never block compute. The backend has no dependency on Kubo's health. ## Proposals [#proposals] Not yet built: a browser-reachable address for Helia nodes, delegated routing, reprovide-only-pinned, a CID allowlist and takedown denylist for the public gateway, and an IPFS storage adapter for provenance verification (issues [#62](https://github.com/QMeshPy/distributed-quantum/issues/62) to [#74](https://github.com/QMeshPy/distributed-quantum/issues/74)). # Overview (/docs/integrations/overview) QMesh connects to payment rails, blockchains, storage networks and AI providers. This page lists them all and says honestly where each one stands. It was written from the pull requests and issues of [QMeshPy/distributed-quantum](https://github.com/QMeshPy/distributed-quantum) as they stood on **1 November 2026**. Those change daily, so check the linked pull request for the current state. Every chain and payment integration below targets a **test network** or Stripe **test mode**, and quantum execution is simulated. The project's own ground rules say so: testnet first, and no feature is described as running on quantum hardware. See [Scope](/docs/scope). ## The three states [#the-three-states] | State | Meaning | | --- | --- | | **In main** | The pull request is merged. The code is in the default branch. It may still be switched off by default. | | **In review** | A pull request is open and not merged. Details can change. | | **Proposed** | Only an issue exists. It is a plan, not a feature. | ## In main [#in-main] | Integration | What it does | Pull requests | Page | | --- | --- | --- | --- | | Algorand x402 | Pay per run in USDC on Algorand TestNet through `402 Payment Required` | [#81](https://github.com/QMeshPy/distributed-quantum/pull/81) | [x402](/docs/integrations/x402) | | Stripe subscriptions | Upgrade past the free trial with Stripe Checkout | [#42](https://github.com/QMeshPy/distributed-quantum/pull/42) | [Billing](/docs/integrations/billing) | | Credit packs and metered runs | Buy credits, spend them on circuit runs | [#84](https://github.com/QMeshPy/distributed-quantum/pull/84) | [Billing](/docs/integrations/billing) | | Stablecoin credit packs | Pay for credit packs in USDC through Stripe | [#88](https://github.com/QMeshPy/distributed-quantum/pull/88) | [Billing](/docs/integrations/billing) | | Payment events and credit ledger | One append-only, USDC-denominated ledger for every rail | [#79](https://github.com/QMeshPy/distributed-quantum/pull/79) | [Credits and ledger](/docs/integrations/credits-ledger) | | Chain registry | One place that knows each network, its explorers, RPC and what it may do | [#39](https://github.com/QMeshPy/distributed-quantum/pull/39), [#92](https://github.com/QMeshPy/distributed-quantum/pull/92), [#93](https://github.com/QMeshPy/distributed-quantum/pull/93), [#102](https://github.com/QMeshPy/distributed-quantum/pull/102) | [Chain registry](/docs/integrations/chain-registry) | | Chainlink price feeds | Verifiable ETH/USD price inputs for finance and risk | [#98](https://github.com/QMeshPy/distributed-quantum/pull/98) | [Chainlink](/docs/integrations/chainlink) | | Robinhood tokenized stocks | Optimize a portfolio of stock tokens from Chainlink price history | [#106](https://github.com/QMeshPy/distributed-quantum/pull/106), [#107](https://github.com/QMeshPy/distributed-quantum/pull/107), [#108](https://github.com/QMeshPy/distributed-quantum/pull/108), [#115](https://github.com/QMeshPy/distributed-quantum/pull/115) | [Chainlink](/docs/integrations/chainlink) | | Provenance bundles | Hashed, verifiable records of runs | [#34](https://github.com/QMeshPy/distributed-quantum/pull/34) to [#37](https://github.com/QMeshPy/distributed-quantum/pull/37) | [Provenance](/docs/concepts/provenance) | | Ethereum anchoring | Merkle roots written to Ethereum Sepolia | [#113](https://github.com/QMeshPy/distributed-quantum/pull/113), [#114](https://github.com/QMeshPy/distributed-quantum/pull/114) | [Provenance](/docs/concepts/provenance) | | ENS | Resolve names, show names, a worker-subname proof of concept | [#97](https://github.com/QMeshPy/distributed-quantum/pull/97) | [ENS](/docs/integrations/ens) | | Sign-in with Ethereum | Wallet sign-in beside email codes | [#100](https://github.com/QMeshPy/distributed-quantum/pull/100) | [Sign-in with Ethereum](/docs/integrations/siwe) | | Starknet session keys | Agents spend STRK under on-chain caps | [#101](https://github.com/QMeshPy/distributed-quantum/pull/101), [#118](https://github.com/QMeshPy/distributed-quantum/pull/118) | [Starknet](/docs/integrations/starknet) | | NEAR AI Cloud and other LLM providers | Agent chat on private, attested inference, or Groq or OpenRouter | [#81](https://github.com/QMeshPy/distributed-quantum/pull/81), [#91](https://github.com/QMeshPy/distributed-quantum/pull/91), [#101](https://github.com/QMeshPy/distributed-quantum/pull/101) | [Agent LLM providers](/docs/integrations/agent-llm) | | Toju agent storage | Agents store reports on public IPFS for a capped Base Sepolia payment | [#81](https://github.com/QMeshPy/distributed-quantum/pull/81) | [Toju](/docs/integrations/toju) | | VAULT | A browser content layer: publish circuits and runs, open any CID | [#40](https://github.com/QMeshPy/distributed-quantum/pull/40), [#83](https://github.com/QMeshPy/distributed-quantum/pull/83), [#90](https://github.com/QMeshPy/distributed-quantum/pull/90), [#109](https://github.com/QMeshPy/distributed-quantum/pull/109) | [VAULT and IPFS](/docs/integrations/vault-ipfs) | | Kubo content node | The platform's own IPFS node, with a typed client, gateway reads, storage limits and metrics | [#80](https://github.com/QMeshPy/distributed-quantum/pull/80), [#82](https://github.com/QMeshPy/distributed-quantum/pull/82), [#85](https://github.com/QMeshPy/distributed-quantum/pull/85) to [#87](https://github.com/QMeshPy/distributed-quantum/pull/87), [#94](https://github.com/QMeshPy/distributed-quantum/pull/94), [#95](https://github.com/QMeshPy/distributed-quantum/pull/95), [#96](https://github.com/QMeshPy/distributed-quantum/pull/96) | [Kubo content node](/docs/integrations/kubo) | ## In review [#in-review] | Work | Pull request | Closes issue | | --- | --- | --- | | Pay-per-fragment worker payouts | [#104](https://github.com/QMeshPy/distributed-quantum/pull/104) | [#8](https://github.com/QMeshPy/distributed-quantum/issues/8) | | Stripe Connect Express onboarding for workers | [#122](https://github.com/QMeshPy/distributed-quantum/pull/122) | [#110](https://github.com/QMeshPy/distributed-quantum/issues/110) | | Read-only wallet portfolio VaR and CVaR | [#123](https://github.com/QMeshPy/distributed-quantum/pull/123) | [#19](https://github.com/QMeshPy/distributed-quantum/issues/19) | | EtherCalc workbooks for experiment results (preview) | [#105](https://github.com/QMeshPy/distributed-quantum/pull/105) | tracks [#103](https://github.com/QMeshPy/distributed-quantum/issues/103) | See the [roadmap](/docs/reference/roadmap) for the rest, including ideas that only exist as issues. # Sign-in with Ethereum (/docs/integrations/siwe) Users can sign in with an Ethereum wallet as well as with an emailed code. It uses the Better Auth `siwe` plugin and follows [EIP-4361](https://eips.ethereum.org/EIPS/eip-4361). It arrived in [#100](https://github.com/QMeshPy/distributed-quantum/pull/100). ## How a sign-in works [#how-a-sign-in-works] 1. The console asks the server for a **nonce**. A nonce is single use and expires. 2. The wallet signs an EIP-4361 message that includes the nonce (an EIP-191 `personal_sign`). 3. The server recovers the signer's address from the signature and checks the message: its **domain**, a **chain id** above zero, and its `expirationTime` and `notBefore`. 4. The nonce is consumed atomically. Replaying a signature fails at once. Signature checking uses `@noble/curves` (secp256k1) and `@noble/hashes` (keccak-256), without a heavy wallet library on the server. A successful reverse [ENS](/docs/integrations/ens) lookup fills in the user's name and avatar. ## Linked wallets [#linked-wallets] A signed-in user can attach more than one wallet. **Settings** has a panel to link, unlink and copy addresses, and to see ENS names. | Method and path | Purpose | | --- | --- | | `POST /api/auth/siwe/link` | Link a wallet to the current account after verifying the signature and nonce. Answers `409` if the address belongs to another account. | | `POST /api/auth/siwe/unlink` | Unlink a wallet. A user cannot unlink their **only** way in (no email and one wallet). If the primary wallet is unlinked, the oldest remaining one becomes primary. | | `GET /api/auth/siwe/wallets` | List linked wallets with chain id, primary flag, creation date and ENS name. | ## Configure [#configure] `NEXT_PUBLIC_APP_DOMAIN` sets the expected domain when it differs from the request host. Email codes use `RESEND_API_KEY` and `OTP_EMAIL_FROM`, and sessions are stored in MongoDB (`MONGODB_URI`). See `frontend/.env.example` for the full list. The feature ships with 16 tests covering nonce handling, message parsing, signature recovery, replay, domain, chain and expiry rejection, and the linking rules. # Starknet session keys (/docs/integrations/starknet) An agent that can spend money needs a limit that does not depend on the agent, or on QMesh's servers, behaving. On **Starknet Sepolia** (testnet only), QMesh puts that limit in a Cairo contract. It arrived in [#101](https://github.com/QMeshPy/distributed-quantum/pull/101) and was extended by [#118](https://github.com/QMeshPy/distributed-quantum/pull/118). ## The idea [#the-idea] The **owner** (a person with a Ready wallet) registers a **session** for an agent. The session has a public key, a per-call cap, a rolling daily cap, an expiry and a recipient. The agent holds the matching session key and can only ask the contract to `spend`. The contract rejects any spend over the caps, after expiry, or after the owner revokes the session. ```mermaid sequenceDiagram participant O as Owner (Ready wallet) participant B as QMesh backend participant C as Policy contract participant R as Recipient O->>B: open a session (caps, expiry) B-->>O: register_session calldata O->>C: sign register_session Note over B,C: later, the agent funds a proposal B->>C: spend (signed with the session key, sent by a relayer) C->>C: check per-call cap, daily cap, expiry, revoked C->>R: transfer STRK O->>C: revoke_session (any time) ``` How the console's agent settings map to the contract: `max_per_proposal` is `max_per_call`, and `daily_budget` is `daily_limit`. The console's defaults are 50 STRK per proposal, 200 STRK per day and 90 days. ## Where the money goes [#where-the-money-goes] Agents normally fund proposals in USDC on Base Sepolia. With an **active Starknet session**, funding goes out as **STRK** from the policy contract to the proposal's `researcher_starknet_wallet`. If the session is active and that wallet is missing, the funding fails. It does **not** fall back to USDC. Amounts in STRK and USDC are tracked separately and never mixed. ## What is where [#what-is-where] - **Contract:** `backend/contracts/starknet/src/spending_policy.cairo`. Per-agent rows, ECDSA session-key `spend`, owner-only register and revoke. The owner can also withdraw testnet STRK through **Get STRK**. - **Backend:** `quantum_backend_v2/starknet/` for keys, policy checks and paymaster support. The session key is encrypted at rest with `WALLET_ENCRYPTION_KEY`. - **Console:** on **Agents**, **Open Starknet Session** connects a Ready wallet with starknetkit. The session card shows the caps, status (Active, Revoking, Expired, Revoked), transaction history and **Revoke**. - **Gas:** the AVNU paymaster may sponsor `register_session` only. Everything else is paid by the Ready wallet or the relayer. ## API [#api] | Method and path | Purpose | | --- | --- | | `POST /api/v1/agents/{id}/starknet-session` | Create a session. Returns the `register_session` calldata and an optional paymaster payload. | | `POST /api/v1/agents/{id}/starknet-session/spend` | Preflight a spend against the caps and return `spend` calldata. | | `POST /api/v1/agents/{id}/starknet-session/tx` | Record a transaction hash. | | `POST /api/v1/agents/{id}/starknet-session/txs` | Refresh transaction receipts. | | `POST /api/v1/agents/{id}/starknet-session/revoke` | Mark the session revoked and return the `revoke_session` calldata. | These routes need a signed identity assertion for the agent's owner (a `qn1.` bridge token), not an ordinary bearer token. ## Set it up [#set-it-up] Prerequisites: Python 3.12 or later, Node 22, a funded Ready wallet on Starknet Sepolia, and Scarb 2.19.0 with starkli and sncast to build and deploy the contract. ```txt title="backend/.env" AGENT_AUTH_BRIDGE_SECRET= WALLET_ENCRYPTION_KEY= QB2_STARKNET_SEPOLIA_RPC_URL=https://starknet-sepolia-rpc.publicnode.com STARKNET_POLICY_CONTRACT= STARKNET_SESSION_KEY_SPEND=1 STARKNET_RELAYER_ADDRESS= STARKNET_RELAYER_PRIVATE_KEY= AVNU_PAYMASTER_API_KEY= ``` 1. Deploy the policy contract and fund it with testnet STRK. The constructor takes the owner's Ready address. 2. Set the variables above and restart the backend. 3. Open a session from **Agents**, and approve `register_session` in the Ready wallet. A contract deployed before #118 cannot be upgraded. Agents re-register against the new one. Treat the relayer key like any other secret: it submits spends but cannot exceed the on-chain caps. Starknet support is **testnet only**. Mainnet is out of scope, and so is proving quantum circuits with STARKs, which is research. # Toju agent storage (/docs/integrations/toju) An agent can draft a report in chat and propose storing it on **public IPFS** through the Toju storage server. You ask "Let's store this proposal so we can access it later", the agent prepares an **approval card**, and only the signed-in owner can approve the publication and its tiny testnet payment. It arrived in [#81](https://github.com/QMeshPy/distributed-quantum/pull/81) and is a **testnet** integration. Pinned content is **public**. Public IPFS copies may remain after the pin retention ends. Do not store anything you would not publish. ## The flow [#the-flow] 1. Ask the agent for a draft. The agent shows only the draft. 2. Ask it to store the proposal. It prepares an approval card showing the exact content, the public-IPFS notice, the recipient, the SHA-256 of the content, the 30-day retention and the price. 3. Click **Approve & store** once to publish, or **Cancel**. A plain "yes" applies only to the current pending approval. 4. QMesh pays on Base Sepolia, uploads, retrieves the file and checks its SHA-256. The result reads "Stored and retrieved. SHA-256 verified." with links to the artifact and the payment. 5. The receipt is saved with the chat. For an uncertain outcome use **Check receipt (no new payment)**. QMesh **never repays automatically**. The payment worker checks the on-chain USDC transfer and the downloaded bytes. An upload response alone is not treated as proof. ## Safety limits [#safety-limits] | Limit | Value | | --- | --- | | Export size | 256 KiB | | Approval lifetime | 15 minutes | | Payment cap per export | 24 micro-USDC (`0.000024` test USDC) | | Network | Base Sepolia, faucet USDC | | A mismatched price quote | Fails closed | An agent can also propose exporting one of your own workflow runs by id. The backend reads **your** stored run snapshots and exports selected metadata and results. It does not trust results the model made up. ## What runs where [#what-runs-where] - **Toju server:** a separate Node and Express service with storage, payment, database and lifecycle-job routes. QMesh points at it with `TOJU_API_URL`. The project hosts a shared test instance, which is not an availability guarantee. - **`backend/toju-client`:** a small isolated Node 22 worker that makes and verifies the payment. It is **not** a replacement storage server. - **Backend:** `services/toju_storage.py` and the export routes `GET /api/v1/agents/{id}/exports` and `POST /api/v1/agents/{id}/exports/{proposal_id}`. - **Console:** the approval card and receipt in **Agents → Agent Chat**. ```txt title="backend/.env" TOJU_API_URL=https://your-toju-server.example TOJU_PAYMENT_RECIPIENT=0x... # the server's public Base USDC address TOJU_NODE_BINARY=node # must resolve to Node 22 NETWORK=base-sepolia CDP_API_KEY_ID= CDP_API_KEY_SECRET= CDP_WALLET_SECRET= AGENT_AUTH_BRIDGE_SECRET= # same value as the console ``` Fund the agent's CDP wallet with test USDC from the Circle faucet before approving. Never use mainnet funds. ## Known limits [#known-limits] A completed payment and verified retrieval have been shown on testnet, and one later approved draft was pinned but failed to settle (its uncertain receipt is kept). A full quantum experiment export, the Solana route, Filecoin payments, the renewal email flow and time-based deletion have **not** been verified end to end. Free-tier hosting means cold starts and quotas. The Solana storage route is a separate proposal ([#24](https://github.com/QMeshPy/distributed-quantum/issues/24)). # VAULT and IPFS (/docs/integrations/vault-ipfs) **VAULT** is the console's content layer. Circuits and runs are stored as content-addressed JSON in your browser with [Helia](https://helia.io) (an IPFS implementation in JavaScript), and you can **pin** them to a provider and share a link. Because the address is a hash of the content (a CID), whoever opens it can check they got exactly what was published. It arrived in four pull requests: [#40](https://github.com/QMeshPy/distributed-quantum/pull/40) (Filecoin pinning through Lighthouse), [#83](https://github.com/QMeshPy/distributed-quantum/pull/83) (open any CID), [#90](https://github.com/QMeshPy/distributed-quantum/pull/90) (publish circuits) and [#109](https://github.com/QMeshPy/distributed-quantum/pull/109) (link runs to circuits). The platform's own IPFS node is a separate piece: [Kubo content node](/docs/integrations/kubo). ## Publish a circuit [#publish-a-circuit] On **Lab → New Run**, **Publish circuit to VAULT** stores a record with `name`, `description`, `qasm`, `qubitCount`, `gateCount`, `author`, `publishedAt` and `tags`. The same **Pin** flow used for runs then appears. The circuit shows up in your Circuit Library and can be re-opened by its CID. ## Link runs to circuits [#link-runs-to-circuits] Run records are encoded with `@helia/dag-json`, so the `circuitCid` field is a real IPLD **link** to the circuit record. One root CID covers the run and its circuit. Sharing a completed run stores the OpenQASM as a circuit block if needed, then links it. Older records with a plain string `circuitCid` still open. The run page shows **Circuit (IPLD link)**. ## Open any CID [#open-any-cid] Paste a CID at `/vault/cid`, or open a shared link `/vault/cid/`. If the block is not in your browser yet, VAULT fetches the **raw block** from a list of trustless HTTP gateways, **re-hashes the bytes against the CID**, caches the block, and then decodes and schema-checks the record. A gateway cannot substitute different content for a CID. An invalid CID and an unreachable gateway give different error messages. ```txt title="frontend/.env.local" # Optional. Defaults to Pinata first. NEXT_PUBLIC_IPFS_TRUSTLESS_GATEWAYS=https://gateway.pinata.cloud,https://trustless-gateway.link,https://4everland.io ``` A trustless gateway sees every CID you request, and your IP address. For sensitive shared results, use a gateway you trust. Verification stops substitution. It does not hide what you asked for. ## Pinning [#pinning] Pinning keeps a copy online after you close the tab. Pinata is the default. **Lighthouse** is offered as **Filecoin (via Lighthouse)**: Lighthouse makes the Filecoin deals itself on its default path, and QMesh sends no on-chain transaction. Things to know: - A failed upload no longer shows "Pinned": the pin record is saved only after the upload succeeds. - The pinned CID is the provider's own and can differ from the local CID. The record keeps the vault CID, and the share link uses the gateway CID. - **Unpin only removes the vault record.** Lighthouse has no unpin API, so the file stays stored. - The whole record is uploaded as a **public** file. - Filecoin deal status is not shown or verified. ```txt title="frontend/.env.local" NEXT_PUBLIC_PINATA_JWT= NEXT_PUBLIC_LIGHTHOUSE_KEY= ``` Anything prefixed `NEXT_PUBLIC_` is shipped to the browser, so these two keys are visible to anyone who loads the console. Use keys that are limited to pinning, and rotate them if they leak. ## What is still open [#what-is-still-open] The content layer has a long list of proposals under the [epic #43](https://github.com/QMeshPy/distributed-quantum/issues/43): a server-side pinning broker, pinning as a CAR so the pinned CID equals the Helia CID, versioned record schemas, persistent storage and eviction warnings, export and import as a CAR file, and a Kubo pinning provider. None of those exist yet. See the [roadmap](/docs/reference/roadmap). # x402 payments (/docs/integrations/x402) [x402](https://www.x402.org) uses the HTTP `402 Payment Required` status to let a client pay for a request programmatically. QMesh exposes two paid routes through it. A client asks for a run, learns the price from the `402` answer, pays in USDC on Algorand, and repeats the request with proof of payment. Only then is a job created. This guide covers **TestNet**. The configuration also accepts `mainnet`, but QMesh is a proof of concept on simulators and its payment path has only been exercised on TestNet. ## The two routes [#the-two-routes] | Route | Runs | | --- | --- | | `POST /api/x402/quantum/runs` | A circuit, distributed across worker peers. Body: `{"circuit": ""}`. | | `POST /api/x402/options/runs` | Quantum Amplitude Estimation option pricing, with a classical Black-Scholes baseline. | The options body takes `option_type` (one of `european_call_short`, `european_call_long`, `expand`, `delay`, `abandon`, `patent`, `natural_resource`, `financial_flexibility`), `current_value`, `strike_or_cost`, `time_to_expiry` (years), `volatility` and `risk_free_rate`. All six are required. Both routes answer a paid request with `201 Created`, a `job_id` and the job's initial status. Read the result with `GET /api/x402/quantum/runs/{jobId}` or `GET /api/x402/options/runs/{jobId}`. Reading a result is **not** paid. ## How a paid request flows [#how-a-paid-request-flows] ```mermaid sequenceDiagram participant C as Client participant G as x402 gateway (console) participant F as Facilitator participant A as Algorand participant B as Coordinator C->>G: POST /api/x402/quantum/runs G-->>C: 402 Payment Required (price, receiver, asset) C->>A: sign the USDC payment C->>G: repeat the request with the payment G->>F: verify and settle F->>A: submit the transfer G->>B: create the job (shared secret) B-->>G: job_id G-->>C: 201 Created with job_id ``` The gateway runs in the console's server routes. After a payment settles it calls the coordinator's hidden internal routes, authenticated with the `X-X402-Gateway-Secret` header. The coordinator stores these jobs under a fixed service identity (`x402-algorand`) and does not charge them against credits. ## Configure it [#configure-it] The routes are off unless `X402_ENABLED=true`. Until then they answer `503` with `{"error": "Algorand x402 is not enabled."}`. ```txt title="frontend/.env.local" X402_ENABLED=true X402_NETWORK=testnet X402_AVM_ADDRESS= X402_FACILITATOR_URL=https://facilitator.goplausible.xyz X402_QUANTUM_RUN_PRICE=$0.01 X402_GATEWAY_SECRET= ``` ```txt title="backend/.env" QB2_X402_GATEWAY_SECRET= ``` | Variable | Notes | | --- | --- | | `X402_NETWORK` | `testnet` (default) or `mainnet`. Anything else is a configuration error. | | `X402_AVM_ADDRESS` | Required. A valid Algorand address that receives the payments. Only the public address is needed; never put a mnemonic or private key in this repository. | | `X402_QUANTUM_RUN_PRICE` | A positive USD amount written as `$0.01`, with up to six decimals. Defaults to `$0.01`. It applies to both routes. | | `X402_FACILITATOR_URL` | The service that verifies and settles payments. Defaults to `https://facilitator.goplausible.xyz`. | | `X402_GATEWAY_SECRET` | Required, at least 16 characters. Must equal `QB2_X402_GATEWAY_SECRET` in the backend. | | `NEXT_PUBLIC_BACKEND_URL` | Where the gateway reaches the coordinator. Defaults to `http://localhost:8081`. | With a bad value, the route answers `503` with `Algorand x402 configuration is invalid.` and logs the reason on the server. The receiver account must be funded and opted in to the USDC asset before it can receive a payment. The payer needs TestNet USDC and an opt-in too. ## Check the unpaid response [#check-the-unpaid-response] Without a payment, the route should answer `402`: ```bash curl -i -X POST http://localhost:3000/api/x402/quantum/runs \ -H "Content-Type: application/json" \ -d '{"circuit": "OPENQASM 2.0; include \"qelib1.inc\"; qreg q[1]; h q[0];"}' ``` The `402` response carries the price, the network, the receiver and the asset. An x402-capable client reads it, pays and retries. See the [x402 documentation](https://www.x402.org) for client libraries. ## Discovery [#discovery] Both routes publish x402 Bazaar discovery metadata and carry the `x402-global-challenge` tag, so that discovery-aware clients and agents can find them and read their input schema. # Configuration (/docs/reference/configuration) The backend reads its settings from environment variables. When it starts it also loads a `.env` file from its working directory, so `backend/.env` works. Defaults below are the values in the code. The sample `backend/.env.example` overrides a few of them, and those overrides are marked. ## Service [#service] | Variable | Default | Purpose | | --- | --- | --- | | `QB2_ENVIRONMENT` | `development` | Label shown in health output. | | `QB2_SERVICE_NAME` | `quantum-backend` | Service name in logs and health output. | | `QB2_API_HOST` | `0.0.0.0` | Bind address. | | `QB2_API_PORT` | `8081` | Bind port. | | `QB2_LOG_LEVEL` | `INFO` | Standard Python log level. | | `QB2_JSON_LOGS` | `true` | Structured JSON logs. Set `false` for simpler local logs. | ## Authentication [#authentication] | Variable | Default | Purpose | | --- | --- | --- | | `QB2_AUTH_REQUIRED` | `true` | When `false`, every request is treated as a local administrator. `.env.example` sets `false`. **Never disable this in production.** | | `QB2_ALLOW_DEV_BEARER_TOKENS` | `false` | Accept `Bearer dev-` tokens even when authentication is required. For controlled local testing only. | ## MongoDB [#mongodb] | Variable | Default | Purpose | | --- | --- | --- | | `QB2_MONGODB_TARGET` | `local` | `local` uses `QB2_MONGODB_LOCAL_URI`; `remote` uses `QB2_MONGODB_REMOTE_URI`. | | `QB2_MONGODB_LOCAL_URI` | none | For example `mongodb://127.0.0.1:27017`. | | `QB2_MONGODB_REMOTE_URI` | none | For example an Atlas `mongodb+srv://` URI. | | `QB2_MONGODB_DATABASE` | `quantum_backend_v2` | Database name. `.env.example` sets `qds`. | | `QB2_MONGODB_SERVER_SELECTION_TIMEOUT_MS` | `5000` | How long to wait for a server. | ## Peer log [#peer-log] | Variable | Default | Purpose | | --- | --- | --- | | `QB2_PEER_ID` | `local-dev-peer` | Identity written into the peer log. | | `QB2_PEER_LOG_DIR` | under the backend folder | Where the append-only log is written. | | `QB2_PEER_LOG_FSYNC` | `true` | Flush every write to disk. | ## libp2p [#libp2p] | Variable | Default | Purpose | | --- | --- | --- | | `QB2_LIBP2P_ENABLED` | `true` | Run the libp2p host. | | `QB2_LIBP2P_PEER_ID` | `qb2-dev-peer` | Peer name. | | `QB2_LIBP2P_LISTEN_MULTIADDRS` | `/ip4/0.0.0.0/tcp/4011` | Comma-separated listen addresses. | | `QB2_LIBP2P_ADVERTISE_MULTIADDRS` | empty | Addresses to advertise, if different. | | `QB2_LIBP2P_BOOTSTRAP_PEERS` | empty | Comma-separated peers to connect to first. | | `QB2_LIBP2P_RENDEZVOUS_NAMESPACE` | `quantum-backend` | Prefix for the discovery topics and protocol ids. | | `QB2_LIBP2P_PEERSTORE_PATH` | under the backend folder | SQLite peerstore file. | | `QB2_LIBP2P_ACTIVATE_LISTENERS` | `true` | Start the listeners. | | `QB2_LIBP2P_HEARTBEAT_INTERVAL_SECONDS` | `60` | Heartbeat period (minimum 5). | | `QB2_LIBP2P_STALE_PEER_TTL_SECONDS` | `300` | Time without a message before a peer is stale (minimum 30). | | `QB2_LIBP2P_DEV_SERVICE_PEER_COUNT` | `0` | Embedded worker peers to start in this process (0 to 32). `.env.example` sets `4`. | | `QB2_LIBP2P_DEV_SERVICE_BASE_PORT` | `4021` | First port used by embedded workers. | ## Credits and payments [#credits-and-payments] | Variable | Default | Purpose | | --- | --- | --- | | `QB2_CIRCUIT_JOB_CREDIT_COST` | `0` | Credits held per circuit job. `0` turns metering off. | | `QB2_X402_GATEWAY_SECRET` | none | Enables the hidden internal routes used by the [x402 gateway](/docs/integrations/x402). Must equal the console's `X402_GATEWAY_SECRET`. | | `QB2_BILLING_INTERNAL_SECRET` | none | Shared secret for the console's billing calls. | | `QB2_PAYMENT_RECONCILE_INTERVAL_SECONDS` | `300` | How often the payment reconciliation sweep runs. | ## Content node and provenance [#content-node-and-provenance] | Variable | Default | Purpose | | --- | --- | --- | | `KUBO_RPC_URL` | `http://kubo:5001` | The private RPC address of the Kubo node. | | `KUBO_RPC_SECRET` | none | Private token shared with Kubo. Setting it turns on [bundle publishing](/docs/concepts/provenance). | | `KUBO_RPC_TIMEOUT_SECONDS` | `30` | Maximum 300. | | `KUBO_RPC_MAX_RESPONSE_BYTES` | `4194304` | Maximum 64 MiB. | | `IPFS_GATEWAY_URL` | `http://kubo:8080` | Private gateway used to read content. | | `IPFS_PUBLIC_GATEWAY_URL` | none | Browser-facing gateway origin, used to build "open evidence" links. | | `IPFS_GATEWAY_TIMEOUT_SECONDS` | `30` | | | `IPFS_GATEWAY_MAX_RESPONSE_BYTES` | `4194304` | | | `QB2_ANCHOR_NETWORK` | none | Turns on anchor verification. A registry alias or CAIP-2 id of a test network with the `anchor_root` capability. | | `QB2_ANCHOR_SENDER` | none | Public address of the anchor account. | | `QB2_ANCHOR_PAST_SENDERS` | empty | Comma-separated earlier anchor accounts, kept so old anchors still verify. | | `QB2_ANCHOR_PRIVATE_KEY` | none | A dedicated low-balance key. With it, the backend also sends anchors on a schedule. | | `QB2_ANCHOR_INTERVAL_SECONDS` | `3600` | Scheduled anchoring period. | | `QB2_ANCHOR_MAX_BATCH` | `10000` | Most bundles per batch. | | `QB2_ANCHOR_MAX_FEE_GWEI` | `100` | Fee ceiling for anchor transactions. | ## Chains [#chains] | Variable | Default | Purpose | | --- | --- | --- | | `QB2_CHAIN_REGISTRY_PATH` | the packaged registry | Path to a chain registry file. | | `NETWORK` | none | Wallet network name, for example `base-sepolia`. | ## Agents and wallets [#agents-and-wallets] | Variable | Purpose | | --- | --- | | `AGENT_AUTH_BRIDGE_SECRET` | Shared secret (at least 32 characters) used to sign and check the identity assertions the console sends. Same value in the console and backend. | | `AGENT_LLM_PROVIDER` | `groq`, `openrouter` or `near`. See [Agent LLM providers](/docs/integrations/agent-llm). | | `GROQ_API_KEY`, `GROQ_MODEL` | Groq. The default model is `openai/gpt-oss-20b`. | | `OPENROUTER_API_KEY`, `OPENROUTER_MODEL` | OpenRouter, free models only. | | `NEARAI_API_KEY`, `NEAR_MODEL` | NEAR AI Cloud. The model must support TEE attestation. | | `CDP_API_KEY_ID`, `CDP_API_KEY_SECRET`, `CDP_WALLET_SECRET` | Coinbase CDP server wallets. | | `WALLET_ENCRYPTION_KEY` | Encrypts stored session keys at rest. | | `TOJU_API_URL`, `TOJU_PAYMENT_RECIPIENT`, `TOJU_NODE_BINARY` | [Toju storage](/docs/integrations/toju). | | `ENS_NETWORK`, `ENS_RPC_URL` | [ENS](/docs/integrations/ens). | ## Starknet [#starknet] | Variable | Purpose | | --- | --- | | `STARKNET_POLICY_CONTRACT` | Address of the deployed spending-policy contract. | | `STARKNET_SESSION_KEY_SPEND` | Set to `1` to use session-key spends. | | `STARKNET_RELAYER_ADDRESS`, `STARKNET_RELAYER_PRIVATE_KEY` | The account that submits spends. | | `AVNU_PAYMASTER_API_KEY` | Lets the AVNU paymaster sponsor `register_session`. | | `QB2_STARKNET_SEPOLIA_RPC_URL` | A live Starknet Sepolia RPC. | See [Starknet session keys](/docs/integrations/starknet). ## Chains and prices [#chains-and-prices] | Variable | Purpose | | --- | --- | | `QB2_BASE_SEPOLIA_RPC_URL`, `QB2_ETHEREUM_SEPOLIA_RPC_URL`, `QB2_ROBINHOOD_TESTNET_RPC_URL`, `QB2_STARKNET_SEPOLIA_RPC_URL` | RPC overrides. See [Chain registry](/docs/integrations/chain-registry). | | `QB2_ROBINHOOD_MAINNET_RPC_URL` | Read-only RPC for tokenized-stock prices. See [Chainlink](/docs/integrations/chainlink). | ## Console (frontend) [#console-frontend] These go in `frontend/.env.local` (see `frontend/.env.example`). | Variable | Purpose | | --- | --- | | `NEXT_PUBLIC_BACKEND_URL` | Where the console reaches the coordinator. | | `NEXT_PUBLIC_TRIAL_DISABLED` | `true` skips the one-hour trial gate locally. | | `MONGODB_URI`, `MONGODB_DB_NAME` | Sign-in sessions. | | `BETTER_AUTH_URL`, `BETTER_AUTH_SECRET` | Better Auth. | | `RESEND_API_KEY`, `OTP_EMAIL_FROM` | Emailed sign-in codes. | | `NEXT_PUBLIC_APP_DOMAIN` | The expected [Sign-in with Ethereum](/docs/integrations/siwe) domain, if different from the host. | | `X402_ENABLED`, `X402_NETWORK`, `X402_AVM_ADDRESS`, `X402_FACILITATOR_URL`, `X402_QUANTUM_RUN_PRICE`, `X402_GATEWAY_SECRET` | [x402](/docs/integrations/x402). | | `STRIPE_SECRET_KEY`, `STRIPE_PRICE_ID`, `STRIPE_WEBHOOK_SECRET` | Subscriptions. See [Billing](/docs/integrations/billing). | | `STRIPE_CREDIT_PACKS`, `BILLING_INTERNAL_SECRET`, `STRIPE_STABLECOIN_PAYMENTS` | Credit packs and USDC packs. | | `NEXT_PUBLIC_IPFS_TRUSTLESS_GATEWAYS` | Gateways VAULT uses to open a CID. | | `NEXT_PUBLIC_PINATA_JWT`, `NEXT_PUBLIC_LIGHTHOUSE_KEY` | VAULT pinning. **Exposed to the browser.** See [VAULT and IPFS](/docs/integrations/vault-ipfs). | Treat every variable above that holds a secret (`QB2_X402_GATEWAY_SECRET`, `QB2_BILLING_INTERNAL_SECRET`, `KUBO_RPC_SECRET`, `QB2_ANCHOR_PRIVATE_KEY`, `WALLET_ENCRYPTION_KEY`, `STARKNET_RELAYER_PRIVATE_KEY`, every API key) as private. Never expose one to a browser, and never commit a `.env` file. # FAQ (/docs/reference/faq) No. Circuits run on Qiskit statevector simulation. QMesh is a proof of concept for orchestrating quantum operations as network services, not a hardware control stack. The software is: a coordinator and workers exchange discovery messages, reservations and fragments over py-libp2p. In local development the workers run inside the backend process, so it is a controlled demo network. An example script can start an external node, but an open network of third-party nodes is not finished. No. Payments use Algorand TestNet USDC, and every other chain QMesh touches is a test network. That a recorded result has not changed since it was recorded. It does not prove hardware execution, scientific correctness or peer honesty. See [Provenance](/docs/concepts/provenance). OpenQASM 2 or 3 style text. Each operation is mapped to one of eleven service types, and any gate the parser does not recognize becomes a `programmable_gate`. See [Circuit planning](/docs/concepts/circuit-planning). Read the `error` field on the job. The usual causes are a circuit the parser rejects, no peer offering a required service, or a block that failed on every assigned peer. See [Jobs](/docs/concepts/jobs). No. Every example in [Basic usage](/docs/basic-usage) calls the backend directly. The x402 payment gateway is the exception: it runs in the console's server routes. Algorand (x402), Base Sepolia, Ethereum Sepolia, Starknet Sepolia and Robinhood Chain (read-only), plus Stripe, Chainlink, ENS, NEAR AI Cloud, Groq, OpenRouter, Coinbase CDP, IPFS and Filecoin through Lighthouse. Most are testnets. See the [integrations overview](/docs/integrations/overview). Pages describe what is merged into `main`. Work in an open pull request is listed on the [roadmap](/docs/reference/roadmap) and flagged where it would change a page. Yes, under Apache 2.0. The code is at [QMeshPy/distributed-quantum](https://github.com/QMeshPy/distributed-quantum). # HTTP API (/docs/reference/http-api) The coordinator serves a REST API under `/api/v1`. The authoritative, always-current reference is the OpenAPI page the backend generates at `/docs` (for example `http://localhost:8081/docs`). This page is a map of the endpoints that exist and what they are for. Most routes require an authenticated user (see [Scope](/docs/scope#authentication)). Users see their own resources; an administrator sees all. Job statuses for circuits are upper case (`QUEUED`, `COMPLETED`); finance, options and risk jobs use lower case (`queued`, `completed`). ## System [#system] | Method and path | Purpose | | --- | --- | | `GET /api/v1/health` | Liveness, version, uptime and persistence status. | | `GET /api/v1/ready` | Readiness, with active probes of dependencies. | | `GET /api/v1/bootstrap/libp2p` | The libp2p bootstrap plan. | | `GET /api/v1/bootstrap/libp2p/runtime` | A summary of the running libp2p host. | ## Circuits and jobs [#circuits-and-jobs] | Method and path | Purpose | | --- | --- | | `POST /api/v1/circuits/submit` | Submit an OpenQASM circuit. Returns `201` with a `job_id`. | | `GET /api/v1/jobs` | List recent jobs. Query: `limit` (1 to 200, default 50), `status` (repeatable). | | `GET /api/v1/jobs/{job_id}` | Job status and result. Query: `result_detail=full|summary`. | | `GET /api/v1/plans/{plan_id}` | The execution plan for a job. | See [Basic usage](/docs/basic-usage) and [Jobs](/docs/concepts/jobs). ## Peers and services [#peers-and-services] | Method and path | Purpose | | --- | --- | | `GET /api/v1/services` | Services advertised by live peers. | | `GET /api/v1/metrics/fidelity/{node_id}` | Fidelity samples for one node. | | `GET /api/v1/discovery/peers` | Known peers. Query: `include_stale`. | | `GET /api/v1/discovery/peers/{peer_id}` | One peer. | | `GET /api/v1/discovery/topology` | Peers with trust tier, health and staleness. | | `GET /api/v1/discovery/network/topology` | Topology in an older, flat format. | | `GET /api/v1/discovery/stats` | Network statistics. | | `POST /api/v1/enrollment/peers` | Enroll or update a peer. | | `GET /api/v1/enrollment/peers` | List enrollments. Query: `trust_tier`, `status`. | | `GET /api/v1/enrollment/peers/{peer_id}` | One enrollment. | | `POST /api/v1/enrollment/peers/{peer_id}/action` | Admin: `approve`, `reject` or `quarantine`. | See [Peers and discovery](/docs/concepts/peers). ## Reservations [#reservations] | Method and path | Purpose | | --- | --- | | `POST /api/v1/reservations` | Request a reservation. | | `GET /api/v1/reservations/{reservation_id}` | Current state, rebuilt from events. | | `POST /api/v1/reservations/{reservation_id}/cancel` | Cancel a reservation. | See [Reservations](/docs/concepts/reservations). ## Provenance [#provenance] | Method and path | Purpose | | --- | --- | | `GET /api/v1/provenance/{bundle_id}/verify` | Verify a bundle. Owner or administrator only. | See [Provenance](/docs/concepts/provenance). ## Finance, options and risk [#finance-options-and-risk] | Method and path | Purpose | | --- | --- | | `POST /api/v1/finance/submit` | Submit a financial CSV for quantum analysis. | | `GET /api/v1/finance` | List finance jobs. | | `GET /api/v1/finance/{job_id}` | Job status and results. | | `GET /api/v1/finance/{job_id}/comparison` | The quantum-versus-classical comparison report. | | `GET /api/v1/finance/tokenized-assets` | Tokenized stocks available for analysis. | | `POST /api/v1/finance/tokenized-portfolio` | Optimize tokenized stocks from Chainlink price history. | | `POST /api/v1/options/submit` | Submit an options pricing job. | | `POST /api/v1/options/batch` | Run quantum and Black-Scholes pricing for every row of a CSV. | | `GET /api/v1/options` and `GET /api/v1/options/{job_id}` | List jobs; read one. | | `POST /api/v1/risk/submit` | Submit a risk job (JSON). | | `POST /api/v1/risk/submit-csv` | Submit a risk job by uploading a portfolio CSV. | | `GET /api/v1/risk` and `GET /api/v1/risk/{job_id}` | List jobs; read one. | ## Workflows [#workflows] | Method and path | Purpose | | --- | --- | | `POST /api/v1/workflows/runs` | Submit a workflow run. | | `GET /api/v1/workflows/runs` and `GET /api/v1/workflows/runs/{run_id}` | List runs; read one. | | `POST /api/v1/workflows/benchmarks` | Submit a benchmark run. | | `GET /api/v1/workflows/benchmarks/{benchmark_id}` | Read a benchmark run. | ## Credits and chains [#credits-and-chains] | Method and path | Purpose | | --- | --- | | `GET /api/v1/credits/balance` | Your credit balance, derived from the ledger. | | `GET /api/v1/chains` | The configured test networks and the operations each supports. | ## Agents, proposals and the marketplace [#agents-proposals-and-the-marketplace] These routes belong to the agent workspace, which is experimental. Several need a signed identity assertion for the user instead of an ordinary bearer token. | Method and path | Purpose | | --- | --- | | `GET /api/v1/agents/llm-providers` | The language-model providers and whether each is configured. See [Agent LLM providers](/docs/integrations/agent-llm). | | `POST /api/v1/agents`, `GET /api/v1/agents`, `GET /api/v1/agents/{id}` | Create, list and read agents. | | `PUT /api/v1/agents/{id}/config` | Update an agent's spending configuration. | | `POST /api/v1/agents/{id}/chat`, `POST /api/v1/agents/{id}/analyze` | Chat with an agent, or have it analyze a proposal. | | `GET /api/v1/agents/{id}/spending` | What the agent has spent. | | `GET /api/v1/agents/{id}/exports`, `POST /api/v1/agents/{id}/exports/{proposal_id}` | Storage exports. See [Toju](/docs/integrations/toju). | | `POST /api/v1/agents/{id}/starknet-session` and `/spend`, `/tx`, `/txs`, `/revoke` | See [Starknet session keys](/docs/integrations/starknet). | | `POST /api/v1/proposals`, `GET /api/v1/proposals`, `GET /api/v1/proposals/{id}` | Research proposals. | | `POST /api/v1/proposals/{id}/fund`, `GET /api/v1/proposals/{id}/funders` | Fund a proposal, and list who did. | | `POST /api/v1/proposals/{id}/fragments/generate`, `.../fragments/{fragment_id}/claim`, `POST .../results` | Fragments of a proposal and their results. | | `POST /api/v1/marketplace/register-pricing`, `GET /api/v1/marketplace/workers` | Worker pricing and listings. | | `POST /api/v1/marketplace/estimate-cost`, `GET /api/v1/marketplace/worker/{worker_id}/earnings` | Cost estimates and worker earnings. | ## ENS [#ens] | Method and path | Purpose | | --- | --- | | `GET /api/v1/ens/resolve?name=` | Name to address. | | `GET /api/v1/ens/reverse?address=` | Address to primary name. | | `GET /api/v1/ens/workers` and `/{subname}` | The worker-subname proof of concept. See [ENS](/docs/integrations/ens). | ## Payments administration [#payments-administration] | Method and path | Purpose | | --- | --- | | `GET /api/v1/admin/payments/events` and `/{key}` | List payment events, or read one. | | `POST /api/v1/admin/payments/events/{key}/reconcile` | Resolve a pending or uncertain event. | | `POST /api/v1/admin/payments/reconciliation` | Run the reconciliation sweep now. | | `GET /api/v1/admin/payments/accounts/{account_id}/balance` | A balance derived from the ledger. | All of these need the admin role. See [Credits and ledger](/docs/integrations/credits-ledger). ## Not covered here [#not-covered-here] The experimental drug-discovery routes (`/api/v1/pharma/...`), and the hidden internal routes used by the x402 and billing gateways, are not documented on this page. The OpenAPI page lists the ones that are public. The console also serves its own routes: `/api/x402/...` for [x402](/docs/integrations/x402), `/api/billing/...` for [billing](/docs/integrations/billing) and `/api/auth/...` for [sign-in](/docs/integrations/siwe). # Roadmap (/docs/reference/roadmap) This page lists what is **not** in `main` yet, as of **1 November 2026**. It comes from the open pull requests and issues of [QMeshPy/distributed-quantum](https://github.com/QMeshPy/distributed-quantum). The maintainers call the plan a proposal, and the epic ([#1](https://github.com/QMeshPy/distributed-quantum/issues/1)) sets these ground rules for all of it: - **Testnet first.** No mainnet code path is turned on without an explicit maintainer decision. - **No secrets** in the repository, logs, prompts or browser bundles. - **Chain logic stays behind the chain registry.** - **The platform runs on simulators.** No feature is described as running on quantum hardware. What already shipped is on the [integrations overview](/docs/integrations/overview). ## In review [#in-review] | Work | What it changes | Pull request | | --- | --- | --- | | Pay-per-fragment payouts | Workers are paid per fragment in USDC, capped at the job's credit hold. A failed job is now charged for the fragments that finished. | [#104](https://github.com/QMeshPy/distributed-quantum/pull/104) | | Stripe Connect Express | Workers can opt in to cash out to a bank or stablecoin wallet. | [#122](https://github.com/QMeshPy/distributed-quantum/pull/122) | | Wallet portfolio analyzer | VaR and CVaR on an address's on-chain holdings, read-only. | [#123](https://github.com/QMeshPy/distributed-quantum/pull/123) | | EtherCalc workbooks | Persistent, shareable spreadsheets for experiment results, with IPFS versions. A draft, off by default. | [#105](https://github.com/QMeshPy/distributed-quantum/pull/105) | ## Proposed: payments and agents [#proposed-payments-and-agents] | Idea | Issue | | --- | --- | | One x402 paywall across Base, Solana and Algorand, with dynamic pricing | [#5](https://github.com/QMeshPy/distributed-quantum/issues/5) | | Paid x402 endpoints for risk, finance and pharma services | [#6](https://github.com/QMeshPy/distributed-quantum/issues/6) | | A public MCP server that charges by x402, and an agent that pays x402 | [#7](https://github.com/QMeshPy/distributed-quantum/issues/7) | | Escrow-backed reservations | [#9](https://github.com/QMeshPy/distributed-quantum/issues/9) | | Tempo and Machine Payments Protocol sessions | [#11](https://github.com/QMeshPy/distributed-quantum/issues/11) | | Any-token funding and payout-token choice through Uniswap | [#12](https://github.com/QMeshPy/distributed-quantum/issues/12) | | Stripe crypto on-ramp for funding escrow | [#78](https://github.com/QMeshPy/distributed-quantum/issues/78) | | Off-ramp from the ledger balance, and documenting payout modes | [#111](https://github.com/QMeshPy/distributed-quantum/issues/111), [#112](https://github.com/QMeshPy/distributed-quantum/issues/112) | | Quadratic funding rounds for research proposals (a first attempt, [#99](https://github.com/QMeshPy/distributed-quantum/pull/99), was closed unmerged) | [#17](https://github.com/QMeshPy/distributed-quantum/issues/17) | | On-chain optimization bounties with verifier contracts | [#20](https://github.com/QMeshPy/distributed-quantum/issues/20) | | Random re-execution audits of worker fragments | [#27](https://github.com/QMeshPy/distributed-quantum/issues/27) | ## Proposed: more chains [#proposed-more-chains] | Idea | Issue | | --- | --- | | Base mainnet-readiness safeguards, Basenames and portable reputation | [#14](https://github.com/QMeshPy/distributed-quantum/issues/14) | | Escrow, wallets and x402 on Arbitrum | [#15](https://github.com/QMeshPy/distributed-quantum/issues/15) | | OP Stack support (Optimism, Unichain-ready) in the registry | [#16](https://github.com/QMeshPy/distributed-quantum/issues/16) | | Solana wallets, Solana Pay, memo anchors and a Toju SOL storage route | [#24](https://github.com/QMeshPy/distributed-quantum/issues/24) | | Chainlink CCIP cross-chain funding and payout, and CRE-driven escrow release | [#26](https://github.com/QMeshPy/distributed-quantum/issues/26) | | NEAR Intents deposits and TEE-held agent keys (research) | [#30](https://github.com/QMeshPy/distributed-quantum/issues/30) | | Zcash shielded fee payments | [#13](https://github.com/QMeshPy/distributed-quantum/issues/13) | | Hyperliquid read-only market data and backtests | [#32](https://github.com/QMeshPy/distributed-quantum/issues/32) | ## Proposed: the content layer [#proposed-the-content-layer] The epic [#43](https://github.com/QMeshPy/distributed-quantum/issues/43) covers Helia in the browser, Kubo as the platform node and Boxo where needed. Open items include draining the pin retry queue ([#44](https://github.com/QMeshPy/distributed-quantum/issues/44)), a server-side pinning broker ([#45](https://github.com/QMeshPy/distributed-quantum/issues/45) and [#46](https://github.com/QMeshPy/distributed-quantum/issues/46)), pinning as a CAR ([#48](https://github.com/QMeshPy/distributed-quantum/issues/48)), versioned record schemas ([#50](https://github.com/QMeshPy/distributed-quantum/issues/50)), export and import as a CAR ([#52](https://github.com/QMeshPy/distributed-quantum/issues/52)), several spikes (one Helia node per browser, large artefacts, IPNS feeds, Kubo pubsub bridged to GossipSub), a CID allowlist and takedown denylist ([#72](https://github.com/QMeshPy/distributed-quantum/issues/72)) and an IPFS adapter for provenance verification ([#74](https://github.com/QMeshPy/distributed-quantum/issues/74)). ## Where to follow along [#where-to-follow-along] Watch the [epic](https://github.com/QMeshPy/distributed-quantum/issues/1) and the pull request list. Each integration page links the pull requests that built it. # Scope (/docs/scope) QMesh is a proof of concept. This page says plainly what it does and does not do, so you can decide whether it fits what you want to build. ## What is real, and what is not [#what-is-real-and-what-is-not] | Area | Status | | --- | --- | | Quantum execution | **Simulated.** Circuits run on Qiskit statevector simulation. There is no quantum hardware control. | | Peer network | **Real software.** The coordinator and workers talk over py-libp2p (GossipSub for discovery, streams for reservations and fragments). | | Local network | In local development the worker peers are started **inside the backend process** (`QB2_LIBP2P_DEV_SERVICE_PEER_COUNT`). That is a controlled demo network. An example script (`scripts/node-starter-template.py`) can start an external node, but QMesh is not a finished open network of third-party nodes. | | Payments | **Testnet and test mode.** x402 settles USDC on Algorand TestNet, Stripe runs in test mode, and agent payments use Base Sepolia and Starknet Sepolia. | | Provenance anchors | **Testnet only.** Anchoring is off until you set `QB2_ANCHOR_NETWORK`, and only test networks are allowed (Ethereum Sepolia in the packaged registry). | | Drug-discovery pipeline | **Experimental.** Its quantum stages are simulated. It is not covered by these docs. | ## What a result proves [#what-a-result-proves] A provenance bundle proves that a recorded result has not changed since it was recorded. It does **not** prove that the work ran on quantum hardware, that the answer is scientifically correct, that a peer was honest, or that stored data stays available. ## Authentication [#authentication] The backend requires authentication by default. The `backend/.env.example` file sets `QB2_AUTH_REQUIRED=false` for local development, which treats every request as a local administrator. Never run a public deployment that way. ## Integrations [#integrations] QMesh connects to Stripe, Algorand, Chainlink, ENS, Starknet, NEAR AI Cloud, IPFS and more. The [integrations overview](/docs/integrations/overview) lists each one and whether it is in `main`, in review or only proposed. Pages describe `main`. Open pull requests and issues are labelled as plans, not features.