From 25ee1261ad5f03370e8265b68e2df7a1e9a56d8f Mon Sep 17 00:00:00 2001 From: AgentTanuki Date: Sat, 15 Aug 2026 00:38:59 +0000 Subject: [PATCH] test(x402): prove Bazaar extension reaches both facilitator legs --- .../tests_x402_interop/fake_facilitator.py | 26 +++++++++++++++++++ .../test_official_client_interop.py | 16 ++++++++++++ 2 files changed, 42 insertions(+) diff --git a/live/guild/tests_x402_interop/fake_facilitator.py b/live/guild/tests_x402_interop/fake_facilitator.py index 997c18c..3789449 100644 --- a/live/guild/tests_x402_interop/fake_facilitator.py +++ b/live/guild/tests_x402_interop/fake_facilitator.py @@ -11,6 +11,7 @@ """ from __future__ import annotations +import copy import hashlib import threading import time @@ -26,6 +27,10 @@ app = FastAPI(title="fake-x402-facilitator") _settled: dict[str, str] = {} +_bazaar_captures: dict[str, dict[str, Any] | None] = { + "verify": None, + "settle": None, +} _lock = threading.Lock() @@ -35,6 +40,25 @@ class FacilitatorRequest(BaseModel): paymentRequirements: dict[str, Any] +def reset_bazaar_captures() -> None: + """Clear facilitator-observed Bazaar metadata between test payments.""" + with _lock: + _bazaar_captures["verify"] = None + _bazaar_captures["settle"] = None + + +def bazaar_captures() -> dict[str, dict[str, Any] | None]: + """Return an isolated snapshot of metadata received on both legs.""" + with _lock: + return copy.deepcopy(_bazaar_captures) + + +def _capture_bazaar(leg: str, body: FacilitatorRequest) -> None: + extensions = body.paymentPayload.get("extensions") or {} + with _lock: + _bazaar_captures[leg] = copy.deepcopy(extensions.get("bazaar")) + + def _recover_signer(reqs: dict[str, Any], auth: dict[str, Any], signature: str) -> str: extra = reqs.get("extra") or {} @@ -110,6 +134,7 @@ def verify(body: FacilitatorRequest): ok, reason, payer = _validate(body) if not ok: return {"isValid": False, "invalidReason": reason, "payer": payer or None} + _capture_bazaar("verify", body) return {"isValid": True, "payer": payer} @@ -119,6 +144,7 @@ def settle(body: FacilitatorRequest): if not ok: return {"success": False, "errorReason": reason, "payer": payer or None, "transaction": "", "network": NETWORK} + _capture_bazaar("settle", body) nonce = body.paymentPayload["payload"]["authorization"]["nonce"] with _lock: if nonce in _settled: # EIP-3009 contracts reject nonce reuse diff --git a/live/guild/tests_x402_interop/test_official_client_interop.py b/live/guild/tests_x402_interop/test_official_client_interop.py index 927c946..dd0b807 100644 --- a/live/guild/tests_x402_interop/test_official_client_interop.py +++ b/live/guild/tests_x402_interop/test_official_client_interop.py @@ -77,6 +77,15 @@ def test_unpaid_request_gets_v2_challenge(live_stack): def test_official_client_pays_and_gets_result_and_receipt(live_stack): + unpaid = httpx.get(live_stack["guild"] + PAID_PATH) + assert unpaid.status_code == 402 + challenge = json.loads(base64.b64decode( + unpaid.headers[PAYMENT_REQUIRED_HEADER])) + quoted_bazaar = challenge["extensions"]["bazaar"] + + from fake_facilitator import bazaar_captures, reset_bazaar_captures + reset_bazaar_captures() + async def run(): transport = x402_httpx_transport(_official_client()) async with httpx.AsyncClient(transport=transport) as http: @@ -95,6 +104,13 @@ async def run(): payer = Account.from_key(CLIENT_KEY).address assert receipt.payer and receipt.payer.lower() == payer.lower() + # CDP Bazaar indexing depends on the client echoing the server-declared + # extension into the PaymentPayload that reaches BOTH facilitator legs. + # Prove exact preservation, rather than merely checking the unpaid 402. + captured_bazaar = bazaar_captures() + assert captured_bazaar["verify"] == quoted_bazaar + assert captured_bazaar["settle"] == quoted_bazaar + # the service records the settlement with its full identity, and REAL # revenue stays zero — this was a value-less testnet-shaped settlement revenue = httpx.get(live_stack["guild"] + "/billing/revenue").json()