diff --git a/src/opengradient/client/x402_auth.py b/src/opengradient/client/x402_auth.py index 9f54c14..12be16a 100644 --- a/src/opengradient/client/x402_auth.py +++ b/src/opengradient/client/x402_auth.py @@ -12,17 +12,20 @@ from x402.clients.base import x402Client from x402.types import PaymentRequirements, x402PaymentRequiredResponse +# Псевдоним типов для чистоты и прохождения проверок +PaymentSelector = typing.Callable[ + [ + list[PaymentRequirements], + typing.Optional[str], + typing.Optional[str], + typing.Optional[int], + ], + PaymentRequirements, +] class X402Auth(httpx.Auth): """ httpx Auth handler for x402 payment protocol. - - This class implements the httpx Auth interface to handle 402 Payment Required - responses by automatically creating and attaching payment headers. - - Example: - async with httpx.AsyncClient(auth=X402Auth(account=wallet_account)) as client: - response = await client.get("https://api.example.com/paid-resource") """ requires_response_body = True @@ -31,45 +34,31 @@ def __init__( self, account: typing.Any, max_value: typing.Optional[int] = None, - payment_requirements_selector: typing.Optional[ - typing.Callable[ - [ - list[PaymentRequirements], - typing.Optional[str], - typing.Optional[str], - typing.Optional[int], - ], - PaymentRequirements, - ] - ] = None, + payment_requirements_selector: typing.Optional[PaymentSelector] = None, network_filter: typing.Optional[str] = None, + scheme_filter: typing.Optional[str] = None, ): """ - Initialize X402Auth with an Ethereum account for signing payments. - Args: - account: eth_account LocalAccount instance for signing payments - max_value: Optional maximum allowed payment amount in base units - network_filter: Optional network filter for selecting payment requirements - scheme_filter: Optional scheme filter for selecting payment requirements + account: eth_account LocalAccount instance + max_value: Optional maximum allowed payment + network_filter: Optional network filter + scheme_filter: Optional scheme filter """ self.x402_client = x402Client( account, max_value=max_value, - payment_requirements_selector=payment_requirements_selector, # type: ignore + payment_requirements_selector=payment_requirements_selector, ) self.network_filter = network_filter - - async def async_auth_flow(self, request: httpx.Request) -> typing.AsyncGenerator[httpx.Request, httpx.Response]: - """ - Handle authentication flow for x402 payment protocol. - - Args: - request: httpx Request object to be authenticated - - Yields: - httpx Request object with authentication headers attached - """ + self.scheme_filter = scheme_filter + + async def async_auth_flow( + self, request: httpx.Request + ) -> typing.AsyncGenerator[httpx.Request, httpx.Response]: + # Подготавливаем тело запроса для возможности повторной отправки + request.read() + response = yield request if response.status_code == 402: @@ -79,15 +68,25 @@ async def async_auth_flow(self, request: httpx.Request) -> typing.AsyncGenerator payment_response = x402PaymentRequiredResponse(**data) + # Используем оба фильтра selected_requirements = self.x402_client.select_payment_requirements( payment_response.accepts, self.network_filter, + self.scheme_filter, ) - payment_header = self.x402_client.create_payment_header(selected_requirements, payment_response.x402_version) + if not selected_requirements: + logging.error("X402Auth: No compatible payment requirements found") + return + + payment_header = self.x402_client.create_payment_header( + selected_requirements, + payment_response.x402_version + ) request.headers["X-Payment"] = payment_header request.headers["Access-Control-Expose-Headers"] = "X-Payment-Response" + yield request except Exception as e: