From 9849ebff21151ccab792d77c9d4e38d5ef1fbe2d Mon Sep 17 00:00:00 2001 From: Plag Date: Sat, 23 May 2026 23:04:45 -0700 Subject: [PATCH 1/2] Create x402_quantum_payments.ipynb --- x402_quantum_payments.ipynb | 330 ++++++++++++++++++++++++++++++++++++ 1 file changed, 330 insertions(+) create mode 100644 x402_quantum_payments.ipynb diff --git a/x402_quantum_payments.ipynb b/x402_quantum_payments.ipynb new file mode 100644 index 0000000..ad236d0 --- /dev/null +++ b/x402_quantum_payments.ipynb @@ -0,0 +1,330 @@ +{ + "cells": [ + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "# Monetizing Quantum Compute with x402 HTTP Payments\n", + "\n", + "[![Open in Colab](https://colab.research.google.com/assets/colab-badge.svg)](https://colab.research.google.com/github/ionq-samples/getting-started/blob/main/x402_quantum_payments.ipynb)\n", + "\n", + "The [x402 protocol](https://www.x402.org/) brings the HTTP `402 Payment Required` status code to life — letting any API gate access behind stablecoin micropayments, with no subscription or billing account needed.\n", + "\n", + "This notebook shows how quantum compute providers can offer **pay-per-circuit** access to their APIs, and how AI agents can consume quantum resources autonomously.\n", + "\n", + "## Why This Matters for Quantum\n", + "\n", + "Quantum compute has a natural fit with micropayments:\n", + "- **Per-shot pricing** already mirrors per-request billing\n", + "- **AI agents** need autonomous compute access without human billing setup\n", + "- **Low-friction onboarding** — a new user can run a circuit with just a wallet, no account creation\n", + "- **Cross-border access** — USDC payments work globally without merchant accounts\n", + "\n", + "## How x402 Works\n", + "\n", + "```\n", + "1. Client sends request to API\n", + "2. Server returns 402 Payment Required + price quote\n", + "3. Client's wallet signs a USDC payment authorization\n", + "4. Client resends request with payment proof in headers\n", + "5. Server verifies payment on-chain, executes request, returns result\n", + "```\n", + "\n", + "The protocol is open — any API can implement it. Payment settles on Base, Ethereum, or other EVM chains in USDC." + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "# Install dependencies\n", + "!pip install requests qiskit qiskit-ionq" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "import os\n", + "import json\n", + "import requests\n", + "from datetime import datetime\n", + "\n", + "# ── Configuration ─────────────────────────────────────────\n", + "#\n", + "# x402 gateways act as payment middleware between clients\n", + "# and upstream APIs. You can:\n", + "# 1. Self-host a gateway (see github.com/coinbase/x402)\n", + "# 2. Use a hosted gateway like gateway.spraay.app\n", + "#\n", + "# The protocol is the same either way — the client just\n", + "# needs the gateway URL and a funded wallet.\n", + "\n", + "IONQ_API_KEY = os.environ.get(\"IONQ_API_KEY\", \"your_ionq_api_key\")\n", + "\n", + "# Any x402-compatible gateway works here\n", + "X402_GATEWAY = os.environ.get(\n", + " \"X402_GATEWAY_URL\",\n", + " \"https://gateway.spraay.app\" # default: Spraay's hosted gateway\n", + ")\n", + "\n", + "print(f\"Gateway: {X402_GATEWAY}\")\n", + "print(f\"Protocol: x402 (HTTP 402 Payment Required)\")\n", + "print(f\"Settlement: USDC on Base L2\")" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "## Step 1: Discover Available Endpoints\n", + "\n", + "x402 gateways publish a manifest at `/.well-known/x402-manifest` listing all payment-gated endpoints and their per-request prices. This lets agents discover capabilities and costs programmatically before committing funds." + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "def discover_x402_endpoints(gateway_url, category=None):\n", + " \"\"\"\n", + " Query an x402 gateway's manifest for available endpoints.\n", + " \n", + " This is a standard part of the x402 protocol — any compliant\n", + " gateway exposes this endpoint for agent discovery.\n", + " \"\"\"\n", + " response = requests.get(\n", + " f\"{gateway_url}/.well-known/x402-manifest\",\n", + " headers={\"Accept\": \"application/json\"}\n", + " )\n", + " \n", + " if response.status_code == 200:\n", + " manifest = response.json()\n", + " endpoints = manifest.get(\"endpoints\", [])\n", + " if category:\n", + " endpoints = [\n", + " ep for ep in endpoints\n", + " if category.lower() in ep.get(\"path\", \"\").lower()\n", + " or category.lower() in ep.get(\"description\", \"\").lower()\n", + " ]\n", + " return endpoints\n", + " return []\n", + "\n", + "print(\"Querying x402 manifest for compute/inference endpoints...\\n\")\n", + "endpoints = discover_x402_endpoints(X402_GATEWAY, category=\"inference\")\n", + "for ep in endpoints[:5]:\n", + " print(f\" {ep.get('method', 'POST')} {ep.get('path', '')}\")\n", + " print(f\" Price: {ep.get('price', 'N/A')} USDC\")\n", + " print(f\" {ep.get('description', '')}\")\n", + " print()" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "## Step 2: Submit a Quantum Circuit with x402 Payment\n", + "\n", + "The core pattern: send your normal IonQ API request through the gateway with an `X-PAYMENT` header. The gateway handles settlement and proxies the authenticated request upstream." + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "def submit_circuit_x402(gateway_url, circuit_json, shots=100, api_key=None):\n", + " \"\"\"\n", + " Submit a quantum circuit through an x402 payment gateway.\n", + " \n", + " This demonstrates the standard x402 client flow:\n", + " \n", + " 1. POST with X-PAYMENT headers to signal willingness to pay\n", + " 2. If 402 → extract price quote, sign payment, retry\n", + " 3. If 200 → job submitted successfully, payment settled\n", + " \n", + " In production, wallet SDKs (e.g. Coinbase AgentKit, \n", + " Spraay Agent Wallet) automate the signing step.\n", + " \"\"\"\n", + " payload = {\n", + " \"target\": \"simulator\", # or 'qpu.harmony' / 'qpu.aria-1'\n", + " \"shots\": shots,\n", + " \"input\": circuit_json\n", + " }\n", + " \n", + " headers = {\n", + " \"Content-Type\": \"application/json\",\n", + " # x402 protocol headers\n", + " \"X-PAYMENT\": \"x402-usdc-base\",\n", + " \"X-PAYMENT-NETWORK\": \"base\",\n", + " }\n", + " if api_key:\n", + " headers[\"Authorization\"] = f\"apiKey {api_key}\"\n", + " \n", + " response = requests.post(\n", + " f\"{gateway_url}/v0.1/ai-inference\",\n", + " json=payload,\n", + " headers=headers\n", + " )\n", + " \n", + " if response.status_code == 402:\n", + " # Standard x402 quote response\n", + " quote = response.json()\n", + " print(\"── x402 Payment Quote ──────────────────────\")\n", + " print(f\" Amount: {quote.get('price', 'N/A')} USDC\")\n", + " print(f\" Network: {quote.get('network', 'base')}\")\n", + " print(f\" Pay to: {quote.get('payTo', 'N/A')}\")\n", + " print(f\" Expires: {quote.get('expiry', 'N/A')}\")\n", + " print(\"────────────────────────────────────────────\")\n", + " print(\" → In production, the wallet SDK auto-signs here\")\n", + " return {\"status\": \"payment_required\", \"quote\": quote}\n", + " \n", + " elif response.status_code in (200, 201):\n", + " result = response.json()\n", + " print(f\"✅ Job submitted — ID: {result.get('id', 'N/A')}\")\n", + " print(f\" Status: {result.get('status', 'N/A')}\")\n", + " print(f\" Payment settled on-chain ✓\")\n", + " return result\n", + " \n", + " else:\n", + " print(f\"Error {response.status_code}: {response.text[:200]}\")\n", + " return None\n", + "\n", + "\n", + "# ── Bell State circuit ────────────────────────────────────\n", + "bell_state = {\n", + " \"format\": \"ionq.circuit.v0\",\n", + " \"qubits\": 2,\n", + " \"circuit\": [\n", + " {\"gate\": \"h\", \"target\": 0},\n", + " {\"gate\": \"cnot\", \"control\": 0, \"target\": 1}\n", + " ]\n", + "}\n", + "\n", + "print(\"Circuit: H(q0) → CNOT(q0, q1)\")\n", + "print(\"Expected: |00⟩ and |11⟩ with ~50/50 probability\")\n", + "print()\n", + "result = submit_circuit_x402(X402_GATEWAY, bell_state, shots=1000, api_key=IONQ_API_KEY)" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "## Step 3: Qiskit Integration\n", + "\n", + "The x402 layer sits transparently between Qiskit and IonQ — you build circuits normally, then route the submission through the payment gateway." + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "from qiskit import QuantumCircuit\n", + "\n", + "def qiskit_to_ionq_json(qc):\n", + " \"\"\"Convert a Qiskit QuantumCircuit to IonQ native JSON.\"\"\"\n", + " gate_map = {\n", + " 'h': lambda q, _: {\"gate\": \"h\", \"target\": q[0]},\n", + " 'x': lambda q, _: {\"gate\": \"x\", \"target\": q[0]},\n", + " 'y': lambda q, _: {\"gate\": \"y\", \"target\": q[0]},\n", + " 'z': lambda q, _: {\"gate\": \"z\", \"target\": q[0]},\n", + " 'cx': lambda q, _: {\"gate\": \"cnot\", \"control\": q[0], \"target\": q[1]},\n", + " 'rz': lambda q, p: {\"gate\": \"rz\", \"target\": q[0], \"rotation\": p[0]},\n", + " 'ry': lambda q, p: {\"gate\": \"ry\", \"target\": q[0], \"rotation\": p[0]},\n", + " }\n", + " gates = []\n", + " for inst in qc.data:\n", + " name = inst.operation.name\n", + " if name in gate_map:\n", + " qubits = [qc.find_bit(q).index for q in inst.qubits]\n", + " gates.append(gate_map[name](qubits, inst.operation.params))\n", + " return {\"format\": \"ionq.circuit.v0\", \"qubits\": qc.num_qubits, \"circuit\": gates}\n", + "\n", + "# Build a 3-qubit GHZ state\n", + "ghz = QuantumCircuit(3)\n", + "ghz.h(0)\n", + "ghz.cx(0, 1)\n", + "ghz.cx(1, 2)\n", + "\n", + "print(ghz.draw(output='text'))\n", + "print()\n", + "\n", + "ionq_payload = qiskit_to_ionq_json(ghz)\n", + "print(\"Submitting via x402...\")\n", + "result = submit_circuit_x402(X402_GATEWAY, ionq_payload, shots=1000)" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "## Implementing x402 on Your Own API\n", + "\n", + "If you're a quantum compute provider and want to add x402 support, here's the minimal server-side pattern:\n", + "\n", + "```python\n", + "from flask import Flask, request, jsonify\n", + "\n", + "app = Flask(__name__)\n", + "\n", + "@app.route('/v1/jobs', methods=['POST'])\n", + "def submit_job():\n", + " payment = request.headers.get('X-PAYMENT')\n", + " \n", + " if not payment:\n", + " # Standard API key auth — existing flow unchanged\n", + " return process_job_normally(request)\n", + " \n", + " payment_proof = request.headers.get('X-PAYMENT-PROOF')\n", + " if not payment_proof:\n", + " # Return 402 with price quote\n", + " return jsonify({\n", + " 'price': '0.01',\n", + " 'currency': 'USDC',\n", + " 'network': 'base',\n", + " 'payTo': '0xYourAddress',\n", + " 'expiry': 300,\n", + " }), 402\n", + " \n", + " # Verify payment on-chain, then process\n", + " if verify_payment(payment_proof):\n", + " return process_job_normally(request)\n", + " else:\n", + " return jsonify({'error': 'payment_invalid'}), 402\n", + "```\n", + "\n", + "The x402 protocol is additive — it doesn't replace existing auth, it runs alongside it.\n", + "\n", + "## Resources\n", + "\n", + "- [x402 Protocol Specification](https://www.x402.org/) — the open standard\n", + "- [Coinbase x402 Reference Implementation](https://github.com/coinbase/x402) — server + client libraries\n", + "- [Spraay x402 Gateway](https://gateway.spraay.app) — hosted gateway with 115+ endpoints\n", + "- [IonQ API Reference](https://docs.ionq.com/api-reference/v0.2/)" + ] + } + ], + "metadata": { + "kernelspec": { + "display_name": "Python 3", + "language": "python", + "name": "python3" + }, + "language_info": { + "name": "python", + "version": "3.12.0" + } + }, + "nbformat": 4, + "nbformat_minor": 4 +} From a66e17d843fe88252cb113f9c1731aa6ba2a2b99 Mon Sep 17 00:00:00 2001 From: Plag Date: Sun, 24 May 2026 13:34:24 -0700 Subject: [PATCH 2/2] Update x402_quantum_payments.ipynb --- x402_quantum_payments.ipynb | 539 ++++++++++++++++++++++++++---------- 1 file changed, 395 insertions(+), 144 deletions(-) diff --git a/x402_quantum_payments.ipynb b/x402_quantum_payments.ipynb index ad236d0..39eae83 100644 --- a/x402_quantum_payments.ipynb +++ b/x402_quantum_payments.ipynb @@ -10,27 +10,21 @@ "\n", "The [x402 protocol](https://www.x402.org/) brings the HTTP `402 Payment Required` status code to life — letting any API gate access behind stablecoin micropayments, with no subscription or billing account needed.\n", "\n", - "This notebook shows how quantum compute providers can offer **pay-per-circuit** access to their APIs, and how AI agents can consume quantum resources autonomously.\n", + "This notebook walks through the full protocol flow:\n", + "\n", + "1. **Public discovery** — find payment-gated endpoints and their pricing\n", + "2. **Unpaid 402 challenge** — inspect the payment requirement before any money moves\n", + "3. **Wallet approval** — user/agent explicitly authorizes the payment\n", + "4. **Signed payment retry** — resend with cryptographic payment proof\n", + "5. **Job execution** — gateway verifies payment on-chain, forwards to IonQ API\n", "\n", "## Why This Matters for Quantum\n", "\n", "Quantum compute has a natural fit with micropayments:\n", "- **Per-shot pricing** already mirrors per-request billing\n", "- **AI agents** need autonomous compute access without human billing setup\n", - "- **Low-friction onboarding** — a new user can run a circuit with just a wallet, no account creation\n", - "- **Cross-border access** — USDC payments work globally without merchant accounts\n", - "\n", - "## How x402 Works\n", - "\n", - "```\n", - "1. Client sends request to API\n", - "2. Server returns 402 Payment Required + price quote\n", - "3. Client's wallet signs a USDC payment authorization\n", - "4. Client resends request with payment proof in headers\n", - "5. Server verifies payment on-chain, executes request, returns result\n", - "```\n", - "\n", - "The protocol is open — any API can implement it. Payment settles on Base, Ethereum, or other EVM chains in USDC." + "- **Low-friction onboarding** — run a circuit with just a wallet, no account creation\n", + "- **Cross-border access** — USDC payments work globally without merchant accounts" ] }, { @@ -43,6 +37,33 @@ "!pip install requests qiskit qiskit-ionq" ] }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "## Configuration\n", + "\n", + "### Gateway trust boundary\n", + "\n", + "An x402 gateway sits between your client and the upstream API. This means the\n", + "gateway operator can see any headers you send through it — **including API keys**.\n", + "\n", + "Before sending real credentials through any gateway, verify:\n", + "- **Who operates it** — is it the API provider, a known infrastructure partner, or a third party?\n", + "- **What it can see** — the gateway terminates TLS and can read request/response bodies\n", + "- **Whether it's contractually authorized** — does the upstream API provider endorse this gateway?\n", + "\n", + "⚠️ **Do not send real IonQ API keys through a third-party gateway unless you trust\n", + "the operator and have verified the trust relationship.** For this notebook, we use\n", + "the simulator with a placeholder gateway URL. Replace it with a gateway you trust\n", + "before using real credentials or QPU targets.\n", + "\n", + "Gateway options:\n", + "- **Self-host** using the [Coinbase x402 reference implementation](https://github.com/coinbase/x402)\n", + "- **Provider-operated** gateway run by the quantum compute provider (ideal — no third-party trust needed)\n", + "- **Third-party hosted** (e.g., `gateway.spraay.app`) — verify trust before sending API keys" + ] + }, { "cell_type": "code", "execution_count": null, @@ -54,36 +75,125 @@ "import requests\n", "from datetime import datetime\n", "\n", - "# ── Configuration ─────────────────────────────────────────\n", - "#\n", - "# x402 gateways act as payment middleware between clients\n", - "# and upstream APIs. You can:\n", - "# 1. Self-host a gateway (see github.com/coinbase/x402)\n", - "# 2. Use a hosted gateway like gateway.spraay.app\n", - "#\n", - "# The protocol is the same either way — the client just\n", - "# needs the gateway URL and a funded wallet.\n", + "# ── Gateway URL ────────────────────────────────────────────\n", + "# Replace with a gateway you trust. See trust boundary notes above.\n", + "X402_GATEWAY = os.environ.get(\"X402_GATEWAY_URL\", \"YOUR_X402_GATEWAY_URL\")\n", "\n", + "# ── IonQ API Key ──────────────────────────────────────────\n", + "# Only send this through a gateway you trust (see above).\n", "IONQ_API_KEY = os.environ.get(\"IONQ_API_KEY\", \"your_ionq_api_key\")\n", "\n", - "# Any x402-compatible gateway works here\n", - "X402_GATEWAY = os.environ.get(\n", - " \"X402_GATEWAY_URL\",\n", - " \"https://gateway.spraay.app\" # default: Spraay's hosted gateway\n", - ")\n", + "if X402_GATEWAY == \"YOUR_X402_GATEWAY_URL\":\n", + " print(\"⚠️ Set X402_GATEWAY_URL to a gateway you trust before running payment cells.\")\n", + " print(\" Self-host: https://github.com/coinbase/x402\")\n", + " print(\" Or use a provider-operated gateway.\")\n", + "else:\n", + " print(f\"Gateway: {X402_GATEWAY}\")\n", + " print(f\"Protocol: x402 (HTTP 402 Payment Required)\")\n", + " print(f\"Settlement: USDC on Base L2\")" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "## Step 1: Public Discovery\n", + "\n", + "x402 gateways advertise payment-gated endpoints at well-known paths.\n", + "Common discovery endpoints across implementations include:\n", + "\n", + "| Path | Convention |\n", + "|------|------------|\n", + "| `/.well-known/x402` | Coinbase reference implementation |\n", + "| `/.well-known/x402.json` | JSON-specific variant |\n", + "| `/.well-known/x402-manifest` | Extended manifest format |\n", + "\n", + "The discovery step is read-only — no payment, no credentials needed.\n", + "This lets agents inspect what's available and at what price before committing." + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "# Common x402 discovery paths across gateway implementations\n", + "DISCOVERY_PATHS = [\n", + " \"/.well-known/x402\",\n", + " \"/.well-known/x402.json\",\n", + " \"/.well-known/x402-manifest\",\n", + "]\n", + "\n", + "\n", + "def discover_x402_endpoints(gateway_url, keyword=None):\n", + " \"\"\"\n", + " Probe a gateway's well-known paths for endpoint discovery.\n", "\n", - "print(f\"Gateway: {X402_GATEWAY}\")\n", - "print(f\"Protocol: x402 (HTTP 402 Payment Required)\")\n", - "print(f\"Settlement: USDC on Base L2\")" + " Tries common x402 discovery paths and returns the first\n", + " successful response. No credentials or payment needed.\n", + " \"\"\"\n", + " for path in DISCOVERY_PATHS:\n", + " try:\n", + " resp = requests.get(\n", + " f\"{gateway_url}{path}\",\n", + " headers={\"Accept\": \"application/json\"},\n", + " timeout=10,\n", + " )\n", + " if resp.status_code == 200:\n", + " data = resp.json()\n", + " endpoints = data.get(\"endpoints\", data.get(\"resources\", []))\n", + " print(f\"✅ Discovery succeeded at {path}\")\n", + " print(f\" Found {len(endpoints)} endpoint(s)\\n\")\n", + " if keyword:\n", + " endpoints = [\n", + " ep for ep in endpoints\n", + " if keyword.lower() in json.dumps(ep).lower()\n", + " ]\n", + " return {\"discovery_path\": path, \"endpoints\": endpoints}\n", + " except requests.RequestException:\n", + " continue\n", + "\n", + " print(\"❌ No discovery endpoint responded.\")\n", + " print(f\" Tried: {', '.join(DISCOVERY_PATHS)}\")\n", + " return None\n", + "\n", + "\n", + "# Run discovery (no payment, no credentials)\n", + "if X402_GATEWAY != \"YOUR_X402_GATEWAY_URL\":\n", + " discovery = discover_x402_endpoints(X402_GATEWAY, keyword=\"inference\")\n", + " if discovery:\n", + " for ep in discovery[\"endpoints\"][:5]:\n", + " print(f\" {ep.get('method', 'POST')} {ep.get('path', '')}\")\n", + " print(f\" Price: {ep.get('price', 'N/A')} USDC\")\n", + " print(f\" {ep.get('description', '')}\")\n", + " print()\n", + "else:\n", + " print(\"⏭️ Skipped — set X402_GATEWAY_URL first.\")" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ - "## Step 1: Discover Available Endpoints\n", + "## Step 2: Unpaid 402 Challenge Inspection\n", + "\n", + "Before any money moves, send an **unpaid request** to the payment-gated endpoint.\n", + "The gateway returns a `402 Payment Required` response containing the exact payment\n", + "requirement. This is a smoke test — verify the challenge shape before proceeding.\n", "\n", - "x402 gateways publish a manifest at `/.well-known/x402-manifest` listing all payment-gated endpoints and their per-request prices. This lets agents discover capabilities and costs programmatically before committing funds." + "A well-formed 402 challenge should include:\n", + "\n", + "| Field | Description |\n", + "|-------|-------------|\n", + "| `amount` / `price` | Cost in the settlement asset |\n", + "| `asset` / `currency` | Settlement token (e.g., USDC) |\n", + "| `network` | Settlement chain (e.g., base, ethereum) |\n", + "| `payTo` | Recipient address for payment |\n", + "| `resource` | The endpoint being gated |\n", + "| `expiry` | How long the quote is valid |\n", + "\n", + "Also inspect response headers for `Cache-Control` and CORS headers." ] }, { @@ -92,46 +202,138 @@ "metadata": {}, "outputs": [], "source": [ - "def discover_x402_endpoints(gateway_url, category=None):\n", + "def inspect_402_challenge(gateway_url, path, payload=None):\n", " \"\"\"\n", - " Query an x402 gateway's manifest for available endpoints.\n", - " \n", - " This is a standard part of the x402 protocol — any compliant\n", - " gateway exposes this endpoint for agent discovery.\n", + " Send an unpaid request and inspect the 402 challenge.\n", + "\n", + " This is a safe, no-cost validation step. The gateway returns\n", + " the payment requirement without executing anything or moving\n", + " any funds. Use this to verify the gateway is well-behaved\n", + " before authorizing payment.\n", + "\n", + " No API keys are sent in this step.\n", " \"\"\"\n", - " response = requests.get(\n", - " f\"{gateway_url}/.well-known/x402-manifest\",\n", - " headers={\"Accept\": \"application/json\"}\n", + " try:\n", + " resp = requests.post(\n", + " f\"{gateway_url}{path}\",\n", + " json=payload or {},\n", + " headers={\"Content-Type\": \"application/json\"},\n", + " timeout=15,\n", + " )\n", + " except requests.RequestException as e:\n", + " print(f\"❌ Request failed: {e}\")\n", + " return None\n", + "\n", + " print(f\"HTTP {resp.status_code}\")\n", + " print()\n", + "\n", + " if resp.status_code != 402:\n", + " print(f\"⚠️ Expected 402, got {resp.status_code}\")\n", + " print(f\" Body: {resp.text[:300]}\")\n", + " return None\n", + "\n", + " # Parse the 402 challenge body\n", + " try:\n", + " challenge = resp.json()\n", + " except ValueError:\n", + " print(\"⚠️ 402 response is not valid JSON\")\n", + " return None\n", + "\n", + " # Validate expected fields\n", + " expected_fields = [\"price\", \"amount\", \"currency\", \"asset\",\n", + " \"network\", \"payTo\", \"resource\", \"expiry\"]\n", + " print(\"── 402 Challenge Body ──────────────────────────\")\n", + " for key, value in challenge.items():\n", + " marker = \"✅\" if key in expected_fields else \" \"\n", + " print(f\" {marker} {key}: {value}\")\n", + "\n", + " # Check which expected fields are present\n", + " present = [f for f in expected_fields if f in challenge]\n", + " missing = [f for f in expected_fields if f not in challenge]\n", + " print(f\"\\n Present: {', '.join(present) if present else 'none'}\")\n", + " if missing:\n", + " # Some fields may use alternate names, so this is informational\n", + " print(f\" Not found: {', '.join(missing)} (may use alternate field names)\")\n", + "\n", + " # Inspect response headers\n", + " print(\"\\n── Response Headers (relevant) ────────────────\")\n", + " for header in [\"Cache-Control\", \"Access-Control-Allow-Origin\",\n", + " \"Access-Control-Allow-Headers\", \"X-Payment-Required\"]:\n", + " val = resp.headers.get(header)\n", + " if val:\n", + " print(f\" {header}: {val}\")\n", + "\n", + " print(\"────────────────────────────────────────────────\")\n", + " return challenge\n", + "\n", + "\n", + "# Smoke test — no payment, no API key\n", + "if X402_GATEWAY != \"YOUR_X402_GATEWAY_URL\":\n", + " print(\"Sending unpaid request to inspect 402 challenge...\\n\")\n", + " challenge = inspect_402_challenge(\n", + " X402_GATEWAY,\n", + " \"/v0.1/ai-inference\",\n", + " payload={\"target\": \"simulator\", \"shots\": 100, \"input\": {}}\n", " )\n", - " \n", - " if response.status_code == 200:\n", - " manifest = response.json()\n", - " endpoints = manifest.get(\"endpoints\", [])\n", - " if category:\n", - " endpoints = [\n", - " ep for ep in endpoints\n", - " if category.lower() in ep.get(\"path\", \"\").lower()\n", - " or category.lower() in ep.get(\"description\", \"\").lower()\n", - " ]\n", - " return endpoints\n", - " return []\n", - "\n", - "print(\"Querying x402 manifest for compute/inference endpoints...\\n\")\n", - "endpoints = discover_x402_endpoints(X402_GATEWAY, category=\"inference\")\n", - "for ep in endpoints[:5]:\n", - " print(f\" {ep.get('method', 'POST')} {ep.get('path', '')}\")\n", - " print(f\" Price: {ep.get('price', 'N/A')} USDC\")\n", - " print(f\" {ep.get('description', '')}\")\n", - " print()" + "else:\n", + " print(\"⏭️ Skipped — set X402_GATEWAY_URL first.\")" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "## Step 3: Build a Quantum Circuit\n", + "\n", + "Before involving payments, build the circuit payload locally." + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "# Bell State circuit in IonQ's native JSON format\n", + "bell_state = {\n", + " \"format\": \"ionq.circuit.v0\",\n", + " \"qubits\": 2,\n", + " \"circuit\": [\n", + " {\"gate\": \"h\", \"target\": 0},\n", + " {\"gate\": \"cnot\", \"control\": 0, \"target\": 1}\n", + " ]\n", + "}\n", + "\n", + "print(\"Circuit: H(q0) → CNOT(q0, q1)\")\n", + "print(\"Expected output: |00⟩ and |11⟩ with ~50/50 probability\")\n", + "print(f\"\\nPayload:\\n{json.dumps(bell_state, indent=2)}\")" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ - "## Step 2: Submit a Quantum Circuit with x402 Payment\n", + "## Step 4: Signed Payment Submission\n", "\n", - "The core pattern: send your normal IonQ API request through the gateway with an `X-PAYMENT` header. The gateway handles settlement and proxies the authenticated request upstream." + "The full x402 payment flow has three distinct phases:\n", + "\n", + "```\n", + "Phase 1: Unpaid request → 402 challenge (done in Step 2)\n", + "Phase 2: Wallet signs a payment authorization covering the quoted amount\n", + "Phase 3: Retry with signed proof in X-PAYMENT-PROOF header → 200 + result\n", + "```\n", + "\n", + "The code below shows the **complete flow** — but the actual payment signing\n", + "(Phase 2) requires a funded wallet and a client library. We mark the signing\n", + "step as pseudocode.\n", + "\n", + "### Client libraries for payment signing\n", + "\n", + "| Library | Language | Notes |\n", + "|---------|----------|-------|\n", + "| [`@coinbase/x402`](https://github.com/coinbase/x402) | TypeScript | Reference implementation |\n", + "| [Coinbase AgentKit](https://github.com/coinbase/agentkit) | Python/TS | AI agent wallet with x402 support |\n", + "| [Spraay Agent Wallet SDK](https://www.npmjs.com/package/spraay-agent-wallet) | TypeScript | Managed agent wallets on Base |" ] }, { @@ -142,85 +344,123 @@ "source": [ "def submit_circuit_x402(gateway_url, circuit_json, shots=100, api_key=None):\n", " \"\"\"\n", - " Submit a quantum circuit through an x402 payment gateway.\n", - " \n", - " This demonstrates the standard x402 client flow:\n", - " \n", - " 1. POST with X-PAYMENT headers to signal willingness to pay\n", - " 2. If 402 → extract price quote, sign payment, retry\n", - " 3. If 200 → job submitted successfully, payment settled\n", - " \n", - " In production, wallet SDKs (e.g. Coinbase AgentKit, \n", - " Spraay Agent Wallet) automate the signing step.\n", + " Submit a quantum circuit through an x402 gateway.\n", + "\n", + " Implements the full three-phase protocol flow:\n", + "\n", + " Phase 1 — Unpaid request: send the job without payment proof.\n", + " Gateway returns 402 with payment requirement.\n", + "\n", + " Phase 2 — Payment signing (pseudocode): a wallet client library\n", + " signs a USDC authorization covering the quoted amount.\n", + " This step requires a funded wallet and a real client SDK.\n", + "\n", + " Phase 3 — Paid retry: resend with X-PAYMENT-PROOF header containing\n", + " the signed payment. Gateway verifies on-chain and proxies\n", + " the authenticated request to IonQ.\n", + "\n", + " ⚠️ API key trust: if api_key is provided, it is sent to the gateway\n", + " in the Authorization header. Only provide this if you trust the\n", + " gateway operator. See the trust boundary notes at the top.\n", " \"\"\"\n", " payload = {\n", - " \"target\": \"simulator\", # or 'qpu.harmony' / 'qpu.aria-1'\n", + " \"target\": \"simulator\",\n", " \"shots\": shots,\n", - " \"input\": circuit_json\n", + " \"input\": circuit_json,\n", " }\n", - " \n", + "\n", + " # ── Phase 1: Unpaid request → 402 challenge ──────────\n", + " print(\"Phase 1: Sending unpaid request...\")\n", + " resp = requests.post(\n", + " f\"{gateway_url}/v0.1/ai-inference\",\n", + " json=payload,\n", + " headers={\"Content-Type\": \"application/json\"},\n", + " timeout=15,\n", + " )\n", + "\n", + " if resp.status_code != 402:\n", + " print(f\" Unexpected status: {resp.status_code}\")\n", + " return None\n", + "\n", + " challenge = resp.json()\n", + " amount = challenge.get(\"price\") or challenge.get(\"amount\", \"?\")\n", + " network = challenge.get(\"network\", \"?\")\n", + " pay_to = challenge.get(\"payTo\", \"?\")\n", + " print(f\" 402 received — {amount} USDC on {network}\")\n", + " print(f\" Pay to: {pay_to}\")\n", + "\n", + " # ── Phase 2: Payment signing (pseudocode) ────────────\n", + " #\n", + " # In production, a wallet client library handles this:\n", + " #\n", + " # from x402_client import sign_payment\n", + " # payment_proof = sign_payment(\n", + " # wallet=agent_wallet,\n", + " # amount=challenge[\"price\"],\n", + " # asset=\"USDC\",\n", + " # network=challenge[\"network\"],\n", + " # pay_to=challenge[\"payTo\"],\n", + " # resource=challenge.get(\"resource\", \"/v0.1/ai-inference\"),\n", + " # )\n", + " #\n", + " # The returned payment_proof is a signed authorization that the\n", + " # gateway can verify on-chain without the client broadcasting\n", + " # a transaction first — the gateway settles it.\n", + " #\n", + " print(\"\\nPhase 2: Payment signing (pseudocode — requires wallet SDK)\")\n", + " print(\" → In production: wallet signs USDC authorization\")\n", + " print(\" → Libraries: @coinbase/x402, Coinbase AgentKit, etc.\")\n", + " payment_proof = \"PSEUDOCODE_PROOF_PLACEHOLDER\"\n", + "\n", + " # ── Phase 3: Paid retry ──────────────────────────────\n", + " print(\"\\nPhase 3: Retrying with signed payment proof...\")\n", " headers = {\n", " \"Content-Type\": \"application/json\",\n", - " # x402 protocol headers\n", - " \"X-PAYMENT\": \"x402-usdc-base\",\n", - " \"X-PAYMENT-NETWORK\": \"base\",\n", + " \"X-PAYMENT-PROOF\": payment_proof,\n", " }\n", " if api_key:\n", + " # ⚠️ Only include if you trust the gateway operator\n", " headers[\"Authorization\"] = f\"apiKey {api_key}\"\n", - " \n", - " response = requests.post(\n", + "\n", + " resp = requests.post(\n", " f\"{gateway_url}/v0.1/ai-inference\",\n", " json=payload,\n", - " headers=headers\n", + " headers=headers,\n", + " timeout=30,\n", " )\n", - " \n", - " if response.status_code == 402:\n", - " # Standard x402 quote response\n", - " quote = response.json()\n", - " print(\"── x402 Payment Quote ──────────────────────\")\n", - " print(f\" Amount: {quote.get('price', 'N/A')} USDC\")\n", - " print(f\" Network: {quote.get('network', 'base')}\")\n", - " print(f\" Pay to: {quote.get('payTo', 'N/A')}\")\n", - " print(f\" Expires: {quote.get('expiry', 'N/A')}\")\n", - " print(\"────────────────────────────────────────────\")\n", - " print(\" → In production, the wallet SDK auto-signs here\")\n", - " return {\"status\": \"payment_required\", \"quote\": quote}\n", - " \n", - " elif response.status_code in (200, 201):\n", - " result = response.json()\n", - " print(f\"✅ Job submitted — ID: {result.get('id', 'N/A')}\")\n", - " print(f\" Status: {result.get('status', 'N/A')}\")\n", - " print(f\" Payment settled on-chain ✓\")\n", + "\n", + " if resp.status_code in (200, 201):\n", + " result = resp.json()\n", + " print(f\" ✅ Job submitted — ID: {result.get('id', 'N/A')}\")\n", + " print(f\" Status: {result.get('status', 'N/A')}\")\n", " return result\n", - " \n", + " elif resp.status_code == 402:\n", + " print(f\" ⚠️ Still 402 — payment proof was not accepted\")\n", + " print(f\" (Expected with pseudocode placeholder)\")\n", + " return {\"status\": \"payment_proof_required\", \"challenge\": challenge}\n", " else:\n", - " print(f\"Error {response.status_code}: {response.text[:200]}\")\n", + " print(f\" Error {resp.status_code}: {resp.text[:200]}\")\n", " return None\n", "\n", "\n", - "# ── Bell State circuit ────────────────────────────────────\n", - "bell_state = {\n", - " \"format\": \"ionq.circuit.v0\",\n", - " \"qubits\": 2,\n", - " \"circuit\": [\n", - " {\"gate\": \"h\", \"target\": 0},\n", - " {\"gate\": \"cnot\", \"control\": 0, \"target\": 1}\n", - " ]\n", - "}\n", - "\n", - "print(\"Circuit: H(q0) → CNOT(q0, q1)\")\n", - "print(\"Expected: |00⟩ and |11⟩ with ~50/50 probability\")\n", - "print()\n", - "result = submit_circuit_x402(X402_GATEWAY, bell_state, shots=1000, api_key=IONQ_API_KEY)" + "# Run the full flow\n", + "if X402_GATEWAY != \"YOUR_X402_GATEWAY_URL\":\n", + " print(\"── Full x402 Flow: Bell State Circuit ──────────\\n\")\n", + " result = submit_circuit_x402(\n", + " X402_GATEWAY, bell_state, shots=1000,\n", + " # api_key=IONQ_API_KEY # uncomment only for trusted gateways\n", + " )\n", + "else:\n", + " print(\"⏭️ Skipped — set X402_GATEWAY_URL first.\")" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ - "## Step 3: Qiskit Integration\n", + "## Step 5: Qiskit Integration\n", "\n", - "The x402 layer sits transparently between Qiskit and IonQ — you build circuits normally, then route the submission through the payment gateway." + "Build circuits with Qiskit, then convert and submit through the x402 flow." ] }, { @@ -250,6 +490,7 @@ " gates.append(gate_map[name](qubits, inst.operation.params))\n", " return {\"format\": \"ionq.circuit.v0\", \"qubits\": qc.num_qubits, \"circuit\": gates}\n", "\n", + "\n", "# Build a 3-qubit GHZ state\n", "ghz = QuantumCircuit(3)\n", "ghz.h(0)\n", @@ -260,17 +501,24 @@ "print()\n", "\n", "ionq_payload = qiskit_to_ionq_json(ghz)\n", - "print(\"Submitting via x402...\")\n", - "result = submit_circuit_x402(X402_GATEWAY, ionq_payload, shots=1000)" + "print(f\"IonQ JSON:\\n{json.dumps(ionq_payload, indent=2)}\")\n", + "\n", + "# Submit via x402 (same flow as Bell state)\n", + "if X402_GATEWAY != \"YOUR_X402_GATEWAY_URL\":\n", + " print(\"\\n── Full x402 Flow: GHZ Circuit ─────────────────\\n\")\n", + " result = submit_circuit_x402(X402_GATEWAY, ionq_payload, shots=1000)\n", + "else:\n", + " print(\"\\n⏭️ Skipped payment flow — set X402_GATEWAY_URL first.\")" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ - "## Implementing x402 on Your Own API\n", + "## Adding x402 to Your Own Quantum API\n", "\n", - "If you're a quantum compute provider and want to add x402 support, here's the minimal server-side pattern:\n", + "If you're a quantum compute provider, here's the minimal server-side pattern.\n", + "x402 is **additive** — it doesn't replace existing API key auth, it runs alongside it.\n", "\n", "```python\n", "from flask import Flask, request, jsonify\n", @@ -279,38 +527,41 @@ "\n", "@app.route('/v1/jobs', methods=['POST'])\n", "def submit_job():\n", - " payment = request.headers.get('X-PAYMENT')\n", - " \n", - " if not payment:\n", - " # Standard API key auth — existing flow unchanged\n", - " return process_job_normally(request)\n", - " \n", - " payment_proof = request.headers.get('X-PAYMENT-PROOF')\n", - " if not payment_proof:\n", - " # Return 402 with price quote\n", + " # If no payment header, use existing auth flow (unchanged)\n", + " if 'X-PAYMENT-PROOF' not in request.headers:\n", + " if 'Authorization' in request.headers:\n", + " return existing_api_key_flow(request)\n", + " # No auth at all — return 402 challenge\n", " return jsonify({\n", " 'price': '0.01',\n", " 'currency': 'USDC',\n", " 'network': 'base',\n", - " 'payTo': '0xYourAddress',\n", - " 'expiry': 300,\n", + " 'payTo': '0xYourReceiverAddress',\n", + " 'resource': '/v1/jobs',\n", + " 'expiry': 300, # seconds\n", " }), 402\n", - " \n", - " # Verify payment on-chain, then process\n", - " if verify_payment(payment_proof):\n", - " return process_job_normally(request)\n", + "\n", + " # Verify the signed payment proof on-chain\n", + " proof = request.headers['X-PAYMENT-PROOF']\n", + " if verify_usdc_payment(proof, expected_amount='0.01'):\n", + " return process_quantum_job(request)\n", " else:\n", - " return jsonify({'error': 'payment_invalid'}), 402\n", + " return jsonify({'error': 'payment_verification_failed'}), 402\n", "```\n", "\n", - "The x402 protocol is additive — it doesn't replace existing auth, it runs alongside it.\n", + "Key points:\n", + "- Existing API key customers are **unaffected** — the x402 path only activates when payment headers are present\n", + "- The 402 challenge includes `resource` and `expiry` so clients know exactly what they're paying for\n", + "- Payment verification happens on-chain (Base L2) — no credit card processor needed\n", "\n", "## Resources\n", "\n", - "- [x402 Protocol Specification](https://www.x402.org/) — the open standard\n", - "- [Coinbase x402 Reference Implementation](https://github.com/coinbase/x402) — server + client libraries\n", - "- [Spraay x402 Gateway](https://gateway.spraay.app) — hosted gateway with 115+ endpoints\n", - "- [IonQ API Reference](https://docs.ionq.com/api-reference/v0.2/)" + "| Resource | Link |\n", + "|----------|------|\n", + "| x402 Protocol Specification | [x402.org](https://www.x402.org/) |\n", + "| Coinbase x402 Reference (server + client) | [github.com/coinbase/x402](https://github.com/coinbase/x402) |\n", + "| Coinbase AgentKit (wallet + x402 signing) | [github.com/coinbase/agentkit](https://github.com/coinbase/agentkit) |\n", + "| IonQ API Reference | [docs.ionq.com](https://docs.ionq.com/api-reference/v0.2/) |" ] } ],