From 64a5207e33a07146960de95346e26014ae15a929 Mon Sep 17 00:00:00 2001 From: vvillait88 Date: Thu, 14 May 2026 11:19:20 -0700 Subject: [PATCH] flatten payment x402 builders to kwargs; delete *Input wrappers MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Drops the wrapper construction for the three x402 helpers: - validate_x402_network_config(ValidateX402NetworkConfigInput(base_network=X)) → validate_x402_network_config(base_network=X) - verify_x402_request(VerifyX402RequestInput(headers=..., is_cached_address=..., accepted_network=...)) → verify_x402_request(headers=..., is_cached_address=..., accepted_network=...) - process_x402_settle(ProcessX402SettleInput(x402_server=..., payload=..., resource_config=..., resource_meta=..., extension=..., transport_context=...)) → process_x402_settle(x402_server=..., payload=..., resource_config=..., resource_meta=..., extension=..., transport_context=...) Deleted from public exports: ProcessX402SettleInput, VerifyX402RequestInput, ValidateX402NetworkConfigInput. Kept: ProcessX402SettleSuccess, ProcessX402SettleFailure, ProcessX402SettleResult, VerifyX402RequestSuccess, VerifyX402RequestFailure, VerifyX402RequestResult, ClassifiedX402Error — consumers pattern-match on these. Tests: 1031 passed / 3 skipped, 95.10% coverage. ty + ruff clean. examples/multi_rail_merchant.py migrated. Co-Authored-By: Claude Opus 4.7 (1M context) --- agentscore_commerce/payment/__init__.py | 6 - agentscore_commerce/payment/x402_settle.py | 68 ++--- .../payment/x402_validation.py | 46 +-- examples/multi_rail_merchant.py | 43 ++- tests/test_lifted_helpers.py | 275 ++++++++---------- 5 files changed, 178 insertions(+), 260 deletions(-) diff --git a/agentscore_commerce/payment/__init__.py b/agentscore_commerce/payment/__init__.py index 301fcc3..a8c46da 100644 --- a/agentscore_commerce/payment/__init__.py +++ b/agentscore_commerce/payment/__init__.py @@ -58,7 +58,6 @@ from agentscore_commerce.payment.x402_settle import ( ClassifiedX402Error, ProcessX402SettleFailure, - ProcessX402SettleInput, ProcessX402SettleResult, ProcessX402SettleSuccess, classify_orchestration_error, @@ -70,9 +69,7 @@ ) from agentscore_commerce.payment.x402_validation import ( X402_SUPPORTED_BASE_NETWORKS, - ValidateX402NetworkConfigInput, VerifyX402RequestFailure, - VerifyX402RequestInput, VerifyX402RequestResult, VerifyX402RequestSuccess, validate_x402_network_config, @@ -102,7 +99,6 @@ "PaymentRequiredHeaderInput", "PaymentSigner", "ProcessX402SettleFailure", - "ProcessX402SettleInput", "ProcessX402SettleResult", "ProcessX402SettleSuccess", "RailDefinition", @@ -111,9 +107,7 @@ "StripeRail", "TempoChargeRail", "TempoSessionRail", - "ValidateX402NetworkConfigInput", "VerifyX402RequestFailure", - "VerifyX402RequestInput", "VerifyX402RequestResult", "VerifyX402RequestSuccess", "X402AcceptsBlock", diff --git a/agentscore_commerce/payment/x402_settle.py b/agentscore_commerce/payment/x402_settle.py index d32071f..8634fe5 100644 --- a/agentscore_commerce/payment/x402_settle.py +++ b/agentscore_commerce/payment/x402_settle.py @@ -29,27 +29,6 @@ from urllib.parse import urlparse -@dataclass -class ProcessX402SettleInput: - """Input for :func:`process_x402_settle`.""" - - #: The x402 server instance from ``create_x402_server``. - x402_server: Any - #: The verified x402 payload extracted from the X-Payment header. - payload: Any - #: Resource configuration the facilitator validates against (network, price, payTo, - #: asset, max_timeout_seconds, etc.). Shape is x402-server-specific. - resource_config: Any - #: Resource metadata exposed to the facilitator. - resource_meta: dict[str, str] - #: Optional extension to enrich during verify (e.g. Bazaar). - extension: Any = None - #: Transport context for the extension enrich step. Defaults to ``{"method": "POST", - #: "adapter": {"getPath": }, "routePattern": }`` derived from - #: ``resource_meta["url"]``. - transport_context: Any = None - - @dataclass class ProcessX402SettleSuccess: """Success outcome from :func:`process_x402_settle`.""" @@ -322,14 +301,31 @@ def coerce_payment_payload(payload: Any) -> Any: return payload -async def process_x402_settle(input: ProcessX402SettleInput) -> ProcessX402SettleResult: - """Run the x402 verify→settle flow and return a tagged outcome.""" - server = input.x402_server - resource_config = coerce_resource_config(input.resource_config) - payload = coerce_payment_payload(input.payload) +async def process_x402_settle( + *, + x402_server: Any, + payload: Any, + resource_config: Any, + resource_meta: dict[str, str], + extension: Any = None, + transport_context: Any = None, +) -> ProcessX402SettleResult: + """Run the x402 verify→settle flow and return a tagged outcome. + + ``resource_config`` accepts either a ``dict`` (JS-style with ``payTo`` / + ``maxTimeoutSeconds`` camelCase keys) or an x402 ``ResourceConfig`` instance — + dicts are coerced before the build step. + + Set ``extension`` to fold a Bazaar (or other) extension into the verify step; + ``transport_context`` defaults to a POST context derived from + ``resource_meta["url"]`` when an extension is supplied. + """ + server = x402_server + coerced_config = coerce_resource_config(resource_config) + coerced_payload = coerce_payment_payload(payload) try: - built_requirements = server.build_payment_requirements(resource_config) + built_requirements = server.build_payment_requirements(coerced_config) except Exception as err: return ProcessX402SettleFailure(phase="facilitator_error", step="build_requirements", error=err) if not built_requirements: @@ -344,22 +340,22 @@ async def process_x402_settle(input: ProcessX402SettleInput) -> ProcessX402Settl # second argument to ``build_payment_requirements`` rather than as a verify-step # input, but the fold happens at build time — so we replay the build with the # enriched extensions and use those requirements going forward. - if input.extension is not None: - transport_context = input.transport_context - if transport_context is None: - path = urlparse(input.resource_meta["url"]).path - transport_context = { + if extension is not None: + resolved_transport_context = transport_context + if resolved_transport_context is None: + path = urlparse(resource_meta["url"]).path + resolved_transport_context = { "method": "POST", "adapter": {"getPath": lambda: path}, "routePattern": path, } try: - enriched_ext = server.enrich_extensions(input.extension, transport_context) + enriched_ext = server.enrich_extensions(extension, resolved_transport_context) except Exception as err: return ProcessX402SettleFailure(phase="facilitator_error", step="enrich_extensions", error=err) try: built_requirements = server.build_payment_requirements( - resource_config, list(enriched_ext.keys()) if isinstance(enriched_ext, dict) else None + coerced_config, list(enriched_ext.keys()) if isinstance(enriched_ext, dict) else None ) if built_requirements: matched_requirement = built_requirements[0] @@ -370,7 +366,7 @@ async def process_x402_settle(input: ProcessX402SettleInput) -> ProcessX402Settl # — not ``process_payment_request`` (a fictional method that earlier versions of this # helper called and only ever worked against test stubs). try: - verify_result = await server.verify_payment(payload, matched_requirement) + verify_result = await server.verify_payment(coerced_payload, matched_requirement) except Exception as err: return ProcessX402SettleFailure(phase="facilitator_error", step="verify_payment", error=err) @@ -391,7 +387,7 @@ async def process_x402_settle(input: ProcessX402SettleInput) -> ProcessX402Settl return ProcessX402SettleFailure(phase="verify_failed", verify_result=verify_result) try: - settle_result = await server.settle_payment(payload, matched_requirement) + settle_result = await server.settle_payment(coerced_payload, matched_requirement) payment_response_header: str | None = None if settle_result is not None: payment_response_header = base64.b64encode(settle_result_to_json_bytes(settle_result)).decode() diff --git a/agentscore_commerce/payment/x402_validation.py b/agentscore_commerce/payment/x402_validation.py index 2e60c55..ae6aaca 100644 --- a/agentscore_commerce/payment/x402_validation.py +++ b/agentscore_commerce/payment/x402_validation.py @@ -30,23 +30,16 @@ X402_SUPPORTED_BASE_NETWORKS: frozenset[str] = frozenset({networks.base.mainnet.caip2, networks.base.sepolia.caip2}) -@dataclass -class ValidateX402NetworkConfigInput: - """Input for :func:`validate_x402_network_config`.""" - - base_network: str - - -def validate_x402_network_config(input: ValidateX402NetworkConfigInput) -> None: +def validate_x402_network_config(*, base_network: str) -> None: """Boot-time guard: raise if the base network isn't supported. Raises ``ValueError`` with a message that names the unsupported value AND lists the valid options — agents tracking down a misconfigured deploy don't need to grep for the supported list. """ - if input.base_network not in X402_SUPPORTED_BASE_NETWORKS: + if base_network not in X402_SUPPORTED_BASE_NETWORKS: raise ValueError( - f"X402_BASE_NETWORK={input.base_network} is not supported. " + f"X402_BASE_NETWORK={base_network} is not supported. " f"Use one of: {', '.join(sorted(X402_SUPPORTED_BASE_NETWORKS))}" ) @@ -54,19 +47,6 @@ def validate_x402_network_config(input: ValidateX402NetworkConfigInput) -> None: _EVM_ADDRESS_RE = re.compile(r"^0x[0-9a-fA-F]{40}$") -@dataclass -class VerifyX402RequestInput: - """Input for :func:`verify_x402_request`.""" - - #: The incoming request headers (case-insensitive lookup). - headers: dict[str, str] - #: Async lookup that returns ``True`` when the address was minted by this merchant - #: (typically ``pi_cache.has_address``). - is_cached_address: Callable[[str], Awaitable[bool]] - #: The merchant's accepted Base CAIP-2 network. - accepted_network: str - - @dataclass class VerifyX402RequestSuccess: """Successful verification — caller passes ``payload`` straight into ``process_x402_settle``.""" @@ -116,7 +96,12 @@ def _regenerate_body(message: str, user_message: str) -> dict[str, Any]: } -async def verify_x402_request(input: VerifyX402RequestInput) -> VerifyX402RequestResult: +async def verify_x402_request( + *, + headers: dict[str, str], + is_cached_address: Callable[[str], Awaitable[bool]], + accepted_network: str, +) -> VerifyX402RequestResult: """Parse the x402 X-Payment header and validate network + payTo + cache hit. Returns ``VerifyX402RequestSuccess`` when valid; the caller passes ``payload`` @@ -127,7 +112,7 @@ async def verify_x402_request(input: VerifyX402RequestInput) -> VerifyX402Reques Reads the header from ``payment-signature`` first, falling back to ``x-payment`` (both are in the wild as the binary-friendly transport name evolved). """ - header_value = _header_lookup(input.headers, "payment-signature", "x-payment") + header_value = _header_lookup(headers, "payment-signature", "x-payment") if not header_value: return VerifyX402RequestFailure( body=_regenerate_body( @@ -156,14 +141,14 @@ async def verify_x402_request(input: VerifyX402RequestInput) -> VerifyX402Reques signed_network = accepted.get("network") signed_pay_to = accepted.get("payTo") - if not signed_network or signed_network != input.accepted_network: + if not signed_network or signed_network != accepted_network: if signed_network and signed_network.lower().startswith("solana:"): return VerifyX402RequestFailure( body=_regenerate_body( ( f"x402 on {signed_network} is not accepted; " f"Solana payments must use the `solana/charge` rail advertised in the 402 challenge. " - f"This server accepts x402 on {input.accepted_network} only." + f"This server accepts x402 on {accepted_network} only." ), ( "Solana payments are not accepted over x402 at this merchant. " @@ -173,10 +158,7 @@ async def verify_x402_request(input: VerifyX402RequestInput) -> VerifyX402Reques ) return VerifyX402RequestFailure( body=_regenerate_body( - ( - f"Unsupported x402 network {signed_network or ''}; " - f"this server accepts {input.accepted_network}." - ), + (f"Unsupported x402 network {signed_network or ''}; this server accepts {accepted_network}."), ( "The credential signed for an unsupported network. Pick the accepted " "network from the 402 challenge and re-sign." @@ -195,7 +177,7 @@ async def verify_x402_request(input: VerifyX402RequestInput) -> VerifyX402Reques ), ) - if not await input.is_cached_address(signed_pay_to): + if not await is_cached_address(signed_pay_to): return VerifyX402RequestFailure( body=_regenerate_body( "payTo address not found in cache or expired. Request a fresh 402 challenge and retry.", diff --git a/examples/multi_rail_merchant.py b/examples/multi_rail_merchant.py index ca02173..8a51903 100644 --- a/examples/multi_rail_merchant.py +++ b/examples/multi_rail_merchant.py @@ -51,9 +51,6 @@ from agentscore_commerce.identity.fastapi import AgentScoreGate, get_agentscore_data from agentscore_commerce.payment import ( USDC, - ProcessX402SettleInput, - ValidateX402NetworkConfigInput, - VerifyX402RequestInput, build_x402_accepts_for_402, networks, process_x402_settle, @@ -73,7 +70,7 @@ # Boot-time guard: validate the configured x402 networks are in the supported set. # Raises on misconfigured deploys before the first request. -validate_x402_network_config(ValidateX402NetworkConfigInput(base_network=X402_BASE_NETWORK)) +validate_x402_network_config(base_network=X402_BASE_NETWORK) # Singleton Stripe PI / deposit-address cache. Backed by Redis when REDIS_URL is set # (multi-instance deployments need this so a deposit lands on whichever instance @@ -134,32 +131,28 @@ async def purchase(request: Request, assess: dict = Depends(get_agentscore_data) # ────────────────────────────────────────────────────────────────────────── if request.headers.get("payment-signature") or request.headers.get("x-payment"): verified = await verify_x402_request( - VerifyX402RequestInput( - headers=dict(request.headers), - is_cached_address=pi_cache.has_address, - accepted_network=X402_BASE_NETWORK, - ) + headers=dict(request.headers), + is_cached_address=pi_cache.has_address, + accepted_network=X402_BASE_NETWORK, ) if not verified.ok: return JSONResponse(verified.body, status_code=verified.status) settle = await process_x402_settle( - ProcessX402SettleInput( - x402_server=x402_server, - payload=verified.payload, - resource_config={ - "scheme": "exact", - "network": verified.signed_network, - "price": f"${total_usd}", - "payTo": verified.signed_pay_to, - "maxTimeoutSeconds": 300, - }, - resource_meta={ - "url": str(request.url), - "description": "Agent purchase via x402", - "mimeType": "application/json", - }, - ) + x402_server=x402_server, + payload=verified.payload, + resource_config={ + "scheme": "exact", + "network": verified.signed_network, + "price": f"${total_usd}", + "payTo": verified.signed_pay_to, + "maxTimeoutSeconds": 300, + }, + resource_meta={ + "url": str(request.url), + "description": "Agent purchase via x402", + "mimeType": "application/json", + }, ) if not settle.success: return JSONResponse( diff --git a/tests/test_lifted_helpers.py b/tests/test_lifted_helpers.py index fbb1a79..763fc41 100644 --- a/tests/test_lifted_helpers.py +++ b/tests/test_lifted_helpers.py @@ -14,11 +14,8 @@ from agentscore_commerce.payment import ( X402_SUPPORTED_BASE_NETWORKS, ProcessX402SettleFailure, - ProcessX402SettleInput, ProcessX402SettleSuccess, - ValidateX402NetworkConfigInput, VerifyX402RequestFailure, - VerifyX402RequestInput, VerifyX402RequestSuccess, classify_x402_settle_result, networks, @@ -171,12 +168,12 @@ def test_respond_402_layers_payment_required_when_x402_set(): def test_validate_x402_accepts_supported_base(): - validate_x402_network_config(ValidateX402NetworkConfigInput(base_network=networks.base.sepolia.caip2)) + validate_x402_network_config(base_network=networks.base.sepolia.caip2) def test_validate_x402_rejects_unknown_base(): with pytest.raises(ValueError, match="X402_BASE_NETWORK=eip155:9999"): - validate_x402_network_config(ValidateX402NetworkConfigInput(base_network="eip155:9999")) + validate_x402_network_config(base_network="eip155:9999") def test_x402_supported_networks_constants(): @@ -204,11 +201,9 @@ def _x_payment(payload: dict) -> str: @pytest.mark.asyncio async def test_verify_x402_missing_header(): res = await verify_x402_request( - VerifyX402RequestInput( - headers={}, - is_cached_address=_always_true, - accepted_network=networks.base.sepolia.caip2, - ) + headers={}, + is_cached_address=_always_true, + accepted_network=networks.base.sepolia.caip2, ) assert isinstance(res, VerifyX402RequestFailure) assert "missing" in res.body["error"]["message"] @@ -217,11 +212,9 @@ async def test_verify_x402_missing_header(): @pytest.mark.asyncio async def test_verify_x402_bad_base64(): res = await verify_x402_request( - VerifyX402RequestInput( - headers={"X-Payment": "not-base64-json"}, - is_cached_address=_always_true, - accepted_network=networks.base.sepolia.caip2, - ) + headers={"X-Payment": "not-base64-json"}, + is_cached_address=_always_true, + accepted_network=networks.base.sepolia.caip2, ) assert isinstance(res, VerifyX402RequestFailure) assert "valid base64" in res.body["error"]["message"] @@ -231,11 +224,9 @@ async def test_verify_x402_bad_base64(): async def test_verify_x402_unsupported_network(): payload = {"accepted": {"network": "eip155:9999", "payTo": "0x" + "a" * 40}} res = await verify_x402_request( - VerifyX402RequestInput( - headers={"x-payment": _x_payment(payload)}, - is_cached_address=_always_true, - accepted_network=networks.base.sepolia.caip2, - ) + headers={"x-payment": _x_payment(payload)}, + is_cached_address=_always_true, + accepted_network=networks.base.sepolia.caip2, ) assert isinstance(res, VerifyX402RequestFailure) assert "Unsupported x402 network" in res.body["error"]["message"] @@ -245,11 +236,9 @@ async def test_verify_x402_unsupported_network(): async def test_verify_x402_malformed_evm_pay_to(): payload = {"accepted": {"network": networks.base.sepolia.caip2, "payTo": "not-an-address"}} res = await verify_x402_request( - VerifyX402RequestInput( - headers={"x-payment": _x_payment(payload)}, - is_cached_address=_always_true, - accepted_network=networks.base.sepolia.caip2, - ) + headers={"x-payment": _x_payment(payload)}, + is_cached_address=_always_true, + accepted_network=networks.base.sepolia.caip2, ) assert isinstance(res, VerifyX402RequestFailure) assert "malformed accepted.payTo" in res.body["error"]["message"] @@ -259,11 +248,9 @@ async def test_verify_x402_malformed_evm_pay_to(): async def test_verify_x402_pay_to_not_in_cache(): payload = {"accepted": {"network": networks.base.sepolia.caip2, "payTo": "0x" + "f" * 40}} res = await verify_x402_request( - VerifyX402RequestInput( - headers={"x-payment": _x_payment(payload)}, - is_cached_address=_always_false, - accepted_network=networks.base.sepolia.caip2, - ) + headers={"x-payment": _x_payment(payload)}, + is_cached_address=_always_false, + accepted_network=networks.base.sepolia.caip2, ) assert isinstance(res, VerifyX402RequestFailure) assert "not found in cache" in res.body["error"]["message"] @@ -273,11 +260,9 @@ async def test_verify_x402_pay_to_not_in_cache(): async def test_verify_x402_failures_carry_regenerate_next_steps(): """Every failure path emits next_steps with regenerate_payment_credential + user_message + warning.""" res = await verify_x402_request( - VerifyX402RequestInput( - headers={}, - is_cached_address=_always_true, - accepted_network=networks.base.sepolia.caip2, - ) + headers={}, + is_cached_address=_always_true, + accepted_network=networks.base.sepolia.caip2, ) assert isinstance(res, VerifyX402RequestFailure) assert res.body["next_steps"]["action"] == "regenerate_payment_credential" @@ -290,11 +275,9 @@ async def test_verify_x402_success_evm(): pay_to = "0x" + "1" * 40 payload = {"accepted": {"network": networks.base.sepolia.caip2, "payTo": pay_to}} res = await verify_x402_request( - VerifyX402RequestInput( - headers={"x-payment": _x_payment(payload)}, - is_cached_address=_always_true, - accepted_network=networks.base.sepolia.caip2, - ) + headers={"x-payment": _x_payment(payload)}, + is_cached_address=_always_true, + accepted_network=networks.base.sepolia.caip2, ) assert isinstance(res, VerifyX402RequestSuccess) assert res.signed_pay_to == pay_to @@ -310,11 +293,9 @@ async def test_verify_x402_rejects_solana_credential(): """ payload = {"accepted": {"network": networks.solana.mainnet.caip2, "payTo": "11111111111111111111111111111111"}} res = await verify_x402_request( - VerifyX402RequestInput( - headers={"x-payment": _x_payment(payload)}, - is_cached_address=_always_true, - accepted_network=networks.base.sepolia.caip2, - ) + headers={"x-payment": _x_payment(payload)}, + is_cached_address=_always_true, + accepted_network=networks.base.sepolia.caip2, ) assert isinstance(res, VerifyX402RequestFailure) msg = res.body["error"]["message"] @@ -376,12 +357,10 @@ async def settle_payment(self, _payload: object, _req: object) -> object: async def test_process_x402_settle_no_requirements(): server = _FakeServer(requirements=[], verify_result={"success": True}) res = await process_x402_settle( - ProcessX402SettleInput( - x402_server=server, - payload={}, - resource_config={}, - resource_meta=_RESOURCE_META, - ) + x402_server=server, + payload={}, + resource_config={}, + resource_meta=_RESOURCE_META, ) assert isinstance(res, ProcessX402SettleFailure) assert res.phase == "no_requirements" @@ -391,12 +370,10 @@ async def test_process_x402_settle_no_requirements(): async def test_process_x402_settle_verify_failed(): server = _FakeServer(requirements=[{"id": "req1"}], verify_result={"success": False, "error": "bad sig"}) res = await process_x402_settle( - ProcessX402SettleInput( - x402_server=server, - payload={}, - resource_config={}, - resource_meta=_RESOURCE_META, - ) + x402_server=server, + payload={}, + resource_config={}, + resource_meta=_RESOURCE_META, ) assert isinstance(res, ProcessX402SettleFailure) assert res.phase == "verify_failed" @@ -410,12 +387,10 @@ async def test_process_x402_settle_settle_failed(): settle_result=RuntimeError("facilitator timeout"), ) res = await process_x402_settle( - ProcessX402SettleInput( - x402_server=server, - payload={}, - resource_config={}, - resource_meta=_RESOURCE_META, - ) + x402_server=server, + payload={}, + resource_config={}, + resource_meta=_RESOURCE_META, ) assert isinstance(res, ProcessX402SettleFailure) assert res.phase == "settle_failed" @@ -430,13 +405,11 @@ async def test_process_x402_settle_success_returns_payment_response_header(): settle_result={"tx_hash": "0xabc", "amount": "110000"}, ) res = await process_x402_settle( - ProcessX402SettleInput( - x402_server=server, - payload={}, - resource_config={}, - resource_meta=_RESOURCE_META, - extension={"name": "bazaar"}, - ) + x402_server=server, + payload={}, + resource_config={}, + resource_meta=_RESOURCE_META, + extension={"name": "bazaar"}, ) assert isinstance(res, ProcessX402SettleSuccess) assert res.matched_requirement == {"id": "req1"} @@ -466,18 +439,16 @@ async def settle_payment(self, _payload: object, _req: object) -> dict: return {"tx_hash": "0xabc"} res = await process_x402_settle( - ProcessX402SettleInput( - x402_server=_CapturingServer(), - payload={}, - resource_config={ - "scheme": "exact", - "network": "eip155:8453", - "price": "$0.10", - "payTo": "0xa43d4e316ef5f430426cd1b454167e5f85e3f4f1", - "maxTimeoutSeconds": 300, - }, - resource_meta=_RESOURCE_META, - ) + x402_server=_CapturingServer(), + payload={}, + resource_config={ + "scheme": "exact", + "network": "eip155:8453", + "price": "$0.10", + "payTo": "0xa43d4e316ef5f430426cd1b454167e5f85e3f4f1", + "maxTimeoutSeconds": 300, + }, + resource_meta=_RESOURCE_META, ) assert isinstance(res, ProcessX402SettleSuccess) # Coerced into x402's ResourceConfig (Pydantic model with snake_case attrs). @@ -514,12 +485,10 @@ async def settle_payment(self, _payload: object, _req: object) -> dict: return {"tx_hash": "0xabc"} res = await process_x402_settle( - ProcessX402SettleInput( - x402_server=_CapturingServer(), - payload={}, - resource_config=typed, - resource_meta=_RESOURCE_META, - ) + x402_server=_CapturingServer(), + payload={}, + resource_config=typed, + resource_meta=_RESOURCE_META, ) assert isinstance(res, ProcessX402SettleSuccess) assert captured["cfg"] is typed @@ -570,18 +539,16 @@ async def settle_payment(self, payload: object, _req: object) -> dict: }, } res = await process_x402_settle( - ProcessX402SettleInput( - x402_server=_CapturingServer(), - payload=payload_dict, - resource_config={ - "scheme": "exact", - "network": "eip155:8453", - "price": "$0.10", - "payTo": "0x" + "00" * 19 + "dE" + "aD", - "maxTimeoutSeconds": 300, - }, - resource_meta=_RESOURCE_META, - ) + x402_server=_CapturingServer(), + payload=payload_dict, + resource_config={ + "scheme": "exact", + "network": "eip155:8453", + "price": "$0.10", + "payTo": "0x" + "00" * 19 + "dE" + "aD", + "maxTimeoutSeconds": 300, + }, + resource_meta=_RESOURCE_META, ) assert isinstance(res, ProcessX402SettleSuccess) # Both verify and settle legs received the typed Pydantic model, not the raw dict. @@ -622,18 +589,16 @@ async def settle_payment(self, _payload: object, _req: object) -> SettleResponse return pydantic_settle res = await process_x402_settle( - ProcessX402SettleInput( - x402_server=_PydanticSettleServer(), - payload={}, - resource_config={ - "scheme": "exact", - "network": "eip155:8453", - "price": "$0.10", - "payTo": "0x" + "00" * 19 + "dE" + "aD", - "maxTimeoutSeconds": 300, - }, - resource_meta=_RESOURCE_META, - ) + x402_server=_PydanticSettleServer(), + payload={}, + resource_config={ + "scheme": "exact", + "network": "eip155:8453", + "price": "$0.10", + "payTo": "0x" + "00" * 19 + "dE" + "aD", + "maxTimeoutSeconds": 300, + }, + resource_meta=_RESOURCE_META, ) assert isinstance(res, ProcessX402SettleSuccess) assert res.payment_response_header is not None @@ -660,18 +625,16 @@ async def settle_payment(self, _payload: object, _req: object) -> dict: return {"success": True, "transaction": "0xdef"} res = await process_x402_settle( - ProcessX402SettleInput( - x402_server=_DictSettleServer(), - payload={}, - resource_config={ - "scheme": "exact", - "network": "eip155:8453", - "price": "$0.10", - "payTo": "0x" + "00" * 19 + "dE" + "aD", - "maxTimeoutSeconds": 300, - }, - resource_meta=_RESOURCE_META, - ) + x402_server=_DictSettleServer(), + payload={}, + resource_config={ + "scheme": "exact", + "network": "eip155:8453", + "price": "$0.10", + "payTo": "0x" + "00" * 19 + "dE" + "aD", + "maxTimeoutSeconds": 300, + }, + resource_meta=_RESOURCE_META, ) assert isinstance(res, ProcessX402SettleSuccess) assert res.payment_response_header is not None @@ -722,18 +685,16 @@ async def settle_payment(self, _payload: object, _req: object) -> dict: return {"tx_hash": "0xabc"} res = await process_x402_settle( - ProcessX402SettleInput( - x402_server=_CapturingServer(), - payload=typed, - resource_config={ - "scheme": "exact", - "network": "eip155:8453", - "price": "$0.10", - "payTo": "0x" + "00" * 19 + "dE" + "aD", - "maxTimeoutSeconds": 300, - }, - resource_meta=_RESOURCE_META, - ) + x402_server=_CapturingServer(), + payload=typed, + resource_config={ + "scheme": "exact", + "network": "eip155:8453", + "price": "$0.10", + "payTo": "0x" + "00" * 19 + "dE" + "aD", + "maxTimeoutSeconds": 300, + }, + resource_meta=_RESOURCE_META, ) assert isinstance(res, ProcessX402SettleSuccess) assert captured["payload"] is typed @@ -751,12 +712,10 @@ async def test_process_x402_settle_wraps_build_requirements_throws_as_facilitato verify_result={"success": True}, ) res = await process_x402_settle( - ProcessX402SettleInput( - x402_server=server, - payload={}, - resource_config={}, - resource_meta=_RESOURCE_META, - ) + x402_server=server, + payload={}, + resource_config={}, + resource_meta=_RESOURCE_META, ) assert isinstance(res, ProcessX402SettleFailure) assert res.phase == "facilitator_error" @@ -772,13 +731,11 @@ async def test_process_x402_settle_wraps_enrich_extensions_throws_as_facilitator enrich_result=RuntimeError("extension barfed"), ) res = await process_x402_settle( - ProcessX402SettleInput( - x402_server=server, - payload={}, - resource_config={}, - resource_meta=_RESOURCE_META, - extension={"name": "bazaar"}, - ) + x402_server=server, + payload={}, + resource_config={}, + resource_meta=_RESOURCE_META, + extension={"name": "bazaar"}, ) assert isinstance(res, ProcessX402SettleFailure) assert res.phase == "facilitator_error" @@ -792,12 +749,10 @@ async def test_process_x402_settle_wraps_verify_payment_throws_as_facilitator_er verify_result=RuntimeError("CDP facilitator: solana:devnet not supported"), ) res = await process_x402_settle( - ProcessX402SettleInput( - x402_server=server, - payload={}, - resource_config={}, - resource_meta=_RESOURCE_META, - ) + x402_server=server, + payload={}, + resource_config={}, + resource_meta=_RESOURCE_META, ) assert isinstance(res, ProcessX402SettleFailure) assert res.phase == "facilitator_error" @@ -813,12 +768,10 @@ async def test_process_x402_settle_does_not_swallow_settle_failed_as_facilitator settle_result=RuntimeError("on-chain rejection"), ) res = await process_x402_settle( - ProcessX402SettleInput( - x402_server=server, - payload={}, - resource_config={}, - resource_meta=_RESOURCE_META, - ) + x402_server=server, + payload={}, + resource_config={}, + resource_meta=_RESOURCE_META, ) assert isinstance(res, ProcessX402SettleFailure) assert res.phase == "settle_failed"