dominusnode 1.1.1__tar.gz → 1.3.0__tar.gz

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (44) hide show
  1. {dominusnode-1.1.1 → dominusnode-1.3.0}/PKG-INFO +1 -1
  2. {dominusnode-1.1.1 → dominusnode-1.3.0}/README.md +12 -12
  3. {dominusnode-1.1.1 → dominusnode-1.3.0}/dominusnode/__init__.py +25 -1
  4. {dominusnode-1.1.1 → dominusnode-1.3.0}/dominusnode/client.py +13 -0
  5. dominusnode-1.3.0/dominusnode/mpp.py +303 -0
  6. dominusnode-1.3.0/dominusnode/x402.py +138 -0
  7. {dominusnode-1.1.1 → dominusnode-1.3.0}/dominusnode.egg-info/PKG-INFO +1 -1
  8. {dominusnode-1.1.1 → dominusnode-1.3.0}/dominusnode.egg-info/SOURCES.txt +1 -0
  9. {dominusnode-1.1.1 → dominusnode-1.3.0}/pyproject.toml +1 -1
  10. dominusnode-1.1.1/dominusnode/x402.py +0 -88
  11. {dominusnode-1.1.1 → dominusnode-1.3.0}/LICENSE +0 -0
  12. {dominusnode-1.1.1 → dominusnode-1.3.0}/dominusnode/admin.py +0 -0
  13. {dominusnode-1.1.1 → dominusnode-1.3.0}/dominusnode/agent_wallet.py +0 -0
  14. {dominusnode-1.1.1 → dominusnode-1.3.0}/dominusnode/auth.py +0 -0
  15. {dominusnode-1.1.1 → dominusnode-1.3.0}/dominusnode/constants.py +0 -0
  16. {dominusnode-1.1.1 → dominusnode-1.3.0}/dominusnode/errors.py +0 -0
  17. {dominusnode-1.1.1 → dominusnode-1.3.0}/dominusnode/http_client.py +0 -0
  18. {dominusnode-1.1.1 → dominusnode-1.3.0}/dominusnode/keys.py +0 -0
  19. {dominusnode-1.1.1 → dominusnode-1.3.0}/dominusnode/plans.py +0 -0
  20. {dominusnode-1.1.1 → dominusnode-1.3.0}/dominusnode/proxy.py +0 -0
  21. {dominusnode-1.1.1 → dominusnode-1.3.0}/dominusnode/py.typed +0 -0
  22. {dominusnode-1.1.1 → dominusnode-1.3.0}/dominusnode/sessions.py +0 -0
  23. {dominusnode-1.1.1 → dominusnode-1.3.0}/dominusnode/slots.py +0 -0
  24. {dominusnode-1.1.1 → dominusnode-1.3.0}/dominusnode/teams.py +0 -0
  25. {dominusnode-1.1.1 → dominusnode-1.3.0}/dominusnode/token_manager.py +0 -0
  26. {dominusnode-1.1.1 → dominusnode-1.3.0}/dominusnode/types.py +0 -0
  27. {dominusnode-1.1.1 → dominusnode-1.3.0}/dominusnode/usage.py +0 -0
  28. {dominusnode-1.1.1 → dominusnode-1.3.0}/dominusnode/wallet.py +0 -0
  29. {dominusnode-1.1.1 → dominusnode-1.3.0}/dominusnode/wallet_auth.py +0 -0
  30. {dominusnode-1.1.1 → dominusnode-1.3.0}/dominusnode.egg-info/dependency_links.txt +0 -0
  31. {dominusnode-1.1.1 → dominusnode-1.3.0}/dominusnode.egg-info/requires.txt +0 -0
  32. {dominusnode-1.1.1 → dominusnode-1.3.0}/dominusnode.egg-info/top_level.txt +0 -0
  33. {dominusnode-1.1.1 → dominusnode-1.3.0}/setup.cfg +0 -0
  34. {dominusnode-1.1.1 → dominusnode-1.3.0}/tests/test_admin.py +0 -0
  35. {dominusnode-1.1.1 → dominusnode-1.3.0}/tests/test_auth.py +0 -0
  36. {dominusnode-1.1.1 → dominusnode-1.3.0}/tests/test_client.py +0 -0
  37. {dominusnode-1.1.1 → dominusnode-1.3.0}/tests/test_errors.py +0 -0
  38. {dominusnode-1.1.1 → dominusnode-1.3.0}/tests/test_keys.py +0 -0
  39. {dominusnode-1.1.1 → dominusnode-1.3.0}/tests/test_proxy.py +0 -0
  40. {dominusnode-1.1.1 → dominusnode-1.3.0}/tests/test_token_manager.py +0 -0
  41. {dominusnode-1.1.1 → dominusnode-1.3.0}/tests/test_usage.py +0 -0
  42. {dominusnode-1.1.1 → dominusnode-1.3.0}/tests/test_wallet.py +0 -0
  43. {dominusnode-1.1.1 → dominusnode-1.3.0}/tests/test_wallet_auth.py +0 -0
  44. {dominusnode-1.1.1 → dominusnode-1.3.0}/tests/test_x402.py +0 -0
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: dominusnode
3
- Version: 1.1.1
3
+ Version: 1.3.0
4
4
  Summary: Official Dominus Node SDK for Python
5
5
  License: MIT
6
6
  Requires-Python: >=3.9
@@ -394,18 +394,18 @@ with DominusNodeClient(base_url="https://api.dominusnode.com") as client:
394
394
  print(f"API error: {e}")
395
395
  ```
396
396
 
397
- | Error Class | HTTP Status | When |
398
- |-------------|-------------|------|
399
- | `AuthenticationError` | 401 | Invalid credentials, expired token |
400
- | `AuthorizationError` | 403 | Insufficient permissions (e.g., non-admin) |
401
- | `InsufficientBalanceError` | 402 | Wallet balance too low |
402
- | `RateLimitError` | 429 | Too many requests |
403
- | `ValidationError` | 400 | Invalid input |
404
- | `NotFoundError` | 404 | Resource not found |
405
- | `ConflictError` | 409 | Duplicate resource |
406
- | `ServerError` | 500+ | Server-side error |
407
- | `NetworkError` | -- | Connection failure, timeout |
408
- | `ProxyError` | -- | Proxy URL building error |
397
+ | Error Class | HTTP Status | When |
398
+ | -------------------------- | ----------- | ------------------------------------------ |
399
+ | `AuthenticationError` | 401 | Invalid credentials, expired token |
400
+ | `AuthorizationError` | 403 | Insufficient permissions (e.g., non-admin) |
401
+ | `InsufficientBalanceError` | 402 | Wallet balance too low |
402
+ | `RateLimitError` | 429 | Too many requests |
403
+ | `ValidationError` | 400 | Invalid input |
404
+ | `NotFoundError` | 404 | Resource not found |
405
+ | `ConflictError` | 409 | Duplicate resource |
406
+ | `ServerError` | 500+ | Server-side error |
407
+ | `NetworkError` | -- | Connection failure, timeout |
408
+ | `ProxyError` | -- | Proxy URL building error |
409
409
 
410
410
  ## Configuration
411
411
 
@@ -34,7 +34,19 @@ from .errors import (
34
34
  ServerError,
35
35
  ValidationError,
36
36
  )
37
- from .x402 import X402Info, X402Facilitator, X402Pricing
37
+ from .x402 import (
38
+ X402Info,
39
+ X402Facilitator,
40
+ X402PaymentHeaders,
41
+ X402Pricing,
42
+ X402_PROTOCOL_VERSION,
43
+ X402_HEADER_PAYMENT_SIGNATURE,
44
+ X402_HEADER_PAYMENT_SIGNATURE_LEGACY,
45
+ X402_HEADER_PAYMENT_RESPONSE,
46
+ X402_HEADER_PAYMENT_RESPONSE_LEGACY,
47
+ X402_HEADER_PAYMENT_REQUIRED,
48
+ )
49
+ from .mpp import MppResource, AsyncMppResource, build_proxy_headers, build_session_headers
38
50
  from .wallet_auth import WalletChallenge, WalletVerifyResult, WalletLinkResult
39
51
  from .types import (
40
52
  ActiveSession,
@@ -141,11 +153,23 @@ __all__ = [
141
153
  # x402
142
154
  "X402Info",
143
155
  "X402Facilitator",
156
+ "X402PaymentHeaders",
144
157
  "X402Pricing",
158
+ "X402_PROTOCOL_VERSION",
159
+ "X402_HEADER_PAYMENT_SIGNATURE",
160
+ "X402_HEADER_PAYMENT_SIGNATURE_LEGACY",
161
+ "X402_HEADER_PAYMENT_RESPONSE",
162
+ "X402_HEADER_PAYMENT_RESPONSE_LEGACY",
163
+ "X402_HEADER_PAYMENT_REQUIRED",
145
164
  # Wallet Auth
146
165
  "WalletChallenge",
147
166
  "WalletVerifyResult",
148
167
  "WalletLinkResult",
168
+ # MPP
169
+ "MppResource",
170
+ "AsyncMppResource",
171
+ "build_proxy_headers",
172
+ "build_session_headers",
149
173
  # Constants
150
174
  "SDK_VERSION",
151
175
  ]
@@ -40,6 +40,7 @@ from .constants import (
40
40
  )
41
41
  from .http_client import AsyncHttpClient, SyncHttpClient
42
42
  from .keys import AsyncKeysResource, KeysResource
43
+ from .mpp import AsyncMppResource, MppResource
43
44
  from .plans import AsyncPlansResource, PlansResource
44
45
  from .proxy import AsyncProxyResource, ProxyResource
45
46
  from .sessions import AsyncSessionsResource, SessionsResource
@@ -130,6 +131,7 @@ class DominusNodeClient:
130
131
  self._teams = TeamsRes(self._http)
131
132
  self._x402 = X402Resource(self._http)
132
133
  self._wallet_auth = WalletAuthResource(self._http, self._token_manager)
134
+ self._mpp = MppResource(self._http)
133
135
 
134
136
  # Auto-connect if credentials provided.
135
137
  # On failure, clear credentials to prevent retention in a partial object.
@@ -210,6 +212,11 @@ class DominusNodeClient:
210
212
  """Wallet-based authentication (EIP-191 signed messages)."""
211
213
  return self._wallet_auth
212
214
 
215
+ @property
216
+ def mpp(self) -> MppResource:
217
+ """MPP (Micropayment Protocol) operations."""
218
+ return self._mpp
219
+
213
220
  # ── Connection methods ───────────────────────────────────────────
214
221
 
215
222
  def connect_with_key(self, api_key: str) -> LoginResult:
@@ -341,6 +348,7 @@ class AsyncDominusNodeClient:
341
348
  self._teams = AsyncTeamsRes(self._http)
342
349
  self._x402 = AsyncX402Resource(self._http)
343
350
  self._wallet_auth = AsyncWalletAuthResource(self._http, self._token_manager)
351
+ self._mpp = AsyncMppResource(self._http)
344
352
 
345
353
  # ── Resource properties ──────────────────────────────────────────
346
354
 
@@ -399,6 +407,11 @@ class AsyncDominusNodeClient:
399
407
  """Wallet-based authentication (EIP-191 signed messages)."""
400
408
  return self._wallet_auth
401
409
 
410
+ @property
411
+ def mpp(self) -> AsyncMppResource:
412
+ """MPP (Micropayment Protocol) operations."""
413
+ return self._mpp
414
+
402
415
  # ── Connection methods ───────────────────────────────────────────
403
416
 
404
417
  async def connect_with_key(self, api_key: str) -> LoginResult:
@@ -0,0 +1,303 @@
1
+ """MPP (Micropayment Protocol) resource -- info, top-ups, payment sessions, and keyless proxy access.
2
+
3
+ MPP allows AI agents to use the proxy WITHOUT any API key -- just pay per request.
4
+ Call ``get_challenge()`` to obtain a credential, then use ``build_proxy_headers()``
5
+ to construct the headers needed for keyless proxy access.
6
+ """
7
+
8
+ from __future__ import annotations
9
+
10
+ import json
11
+ import math
12
+ from typing import Any, Dict
13
+
14
+ # IETF MPP auth scheme names
15
+ MPP_AUTH_SCHEME = "Payment"
16
+ MPP_AUTH_SCHEME_LEGACY = "MPP"
17
+ from urllib.parse import quote
18
+
19
+ from .http_client import AsyncHttpClient, SyncHttpClient
20
+ from .wallet import _validate_amount_cents
21
+
22
+
23
+ def _validate_finite_int(value: int, name: str) -> None:
24
+ """Validate that *value* is a finite integer (guards against NaN/Infinity via float coercion)."""
25
+ if not isinstance(value, int):
26
+ raise ValueError(f"{name} must be an integer")
27
+ # Python int can't be NaN/Inf, but guard against float passed as int subclass
28
+ as_float = float(value)
29
+ if math.isnan(as_float) or math.isinf(as_float):
30
+ raise ValueError(f"{name} must be a finite number")
31
+
32
+
33
+ def build_proxy_headers(credential: Dict[str, Any]) -> Dict[str, str]:
34
+ """Build proxy Authorization header for keyless MPP access.
35
+
36
+ Returns headers that should be set on the HTTP CONNECT / proxy request
37
+ to route through the proxy without an API key.
38
+
39
+ Args:
40
+ credential: The credential dict from ``get_challenge()`` (or a manually
41
+ constructed dict with at least ``challengeId``, ``nonce``, ``poolType``).
42
+
43
+ Returns:
44
+ Headers dict with ``Authorization: MPP <json>``.
45
+ """
46
+ return {
47
+ "Authorization": MPP_AUTH_SCHEME + " " + json.dumps(credential, separators=(",", ":")),
48
+ }
49
+
50
+
51
+ def build_session_headers(session_token: str) -> Dict[str, str]:
52
+ """Build proxy session token header for ongoing MPP sessions.
53
+
54
+ After opening a session, use the session token for subsequent requests
55
+ within the same session.
56
+
57
+ Args:
58
+ session_token: The session token from an active MPP session.
59
+
60
+ Returns:
61
+ Headers dict with ``MPP-SESSION-TOKEN``.
62
+ """
63
+ if not session_token or not isinstance(session_token, str):
64
+ raise ValueError("session_token must be a non-empty string")
65
+ return {
66
+ "MPP-SESSION-TOKEN": session_token,
67
+ }
68
+
69
+
70
+ # ──────────────────────────────────────────────────────────────────────
71
+ # Sync
72
+ # ──────────────────────────────────────────────────────────────────────
73
+
74
+
75
+ class MppResource:
76
+ """Synchronous MPP (Micropayment Protocol) operations.
77
+
78
+ MPP enables keyless proxy access for AI agents: call ``get_challenge()`` to
79
+ obtain a credential, then use ``build_proxy_headers()`` to construct the proxy
80
+ Authorization header -- no API key needed.
81
+ """
82
+
83
+ def __init__(self, http: SyncHttpClient) -> None:
84
+ self._http = http
85
+
86
+ def get_info(self) -> dict:
87
+ """Get MPP protocol information.
88
+
89
+ This endpoint does not require authentication.
90
+
91
+ Returns:
92
+ Server response dict with MPP capabilities and configuration.
93
+ """
94
+ data = self._http.get("/api/mpp/info", requires_auth=False)
95
+ return data
96
+
97
+ def get_challenge(self, pool_type: str) -> dict:
98
+ """Request an MPP challenge for keyless proxy access.
99
+
100
+ This endpoint does not require authentication. The returned credential
101
+ can be used with ``build_proxy_headers()`` to access the proxy without
102
+ an API key.
103
+
104
+ Args:
105
+ pool_type: Proxy pool type -- ``"dc"`` (datacenter, $3/GB) or
106
+ ``"residential"`` ($5/GB).
107
+
108
+ Returns:
109
+ Server response dict with ``challengeId``, ``nonce``, ``expiresAt``,
110
+ ``poolType``, and ``credential``.
111
+ """
112
+ if pool_type not in ("dc", "residential"):
113
+ raise ValueError("pool_type must be 'dc' or 'residential'")
114
+ data = self._http.post(
115
+ "/api/mpp/challenge",
116
+ json={"poolType": pool_type},
117
+ requires_auth=False,
118
+ )
119
+ return data
120
+
121
+ def top_up(self, amount_cents: int, method: str) -> dict:
122
+ """Top up the MPP balance.
123
+
124
+ Requires authentication (wallet credit needs a user account).
125
+
126
+ Args:
127
+ amount_cents: Amount in cents (must be positive, <= 2,147,483,647).
128
+ method: Payment method identifier (e.g. "stripe", "paypal", "crypto").
129
+
130
+ Returns:
131
+ Server response dict with top-up confirmation details.
132
+ """
133
+ _validate_finite_int(amount_cents, "amount_cents")
134
+ _validate_amount_cents(amount_cents, "amount_cents")
135
+ data = self._http.post("/api/mpp/topup", json={
136
+ "amountCents": amount_cents,
137
+ "method": method,
138
+ })
139
+ return data
140
+
141
+ def open_session(self, max_deposit_cents: int, method: str, pool_type: str) -> dict:
142
+ """Open a new MPP payment session (payment channel).
143
+
144
+ This endpoint does not require authentication -- anonymous agents can
145
+ open sessions by providing a payment method and deposit.
146
+
147
+ Args:
148
+ max_deposit_cents: Maximum deposit in cents (must be positive, <= 2,147,483,647).
149
+ method: Payment method identifier.
150
+ pool_type: Proxy pool type (e.g. "dc", "residential").
151
+
152
+ Returns:
153
+ Server response dict with session/channel details.
154
+ """
155
+ _validate_finite_int(max_deposit_cents, "max_deposit_cents")
156
+ _validate_amount_cents(max_deposit_cents, "max_deposit_cents")
157
+ data = self._http.post("/api/mpp/session/open", json={
158
+ "maxDepositCents": max_deposit_cents,
159
+ "method": method,
160
+ "poolType": pool_type,
161
+ }, requires_auth=False)
162
+ return data
163
+
164
+ def close_session(self, channel_id: str) -> dict:
165
+ """Close an existing MPP payment session.
166
+
167
+ Args:
168
+ channel_id: The payment channel identifier.
169
+
170
+ Returns:
171
+ Server response dict with closure confirmation and final settlement.
172
+ """
173
+ data = self._http.post("/api/mpp/session/close", json={
174
+ "channelId": channel_id,
175
+ })
176
+ return data
177
+
178
+ def get_session(self, channel_id: str) -> dict:
179
+ """Get details of an MPP payment session.
180
+
181
+ Args:
182
+ channel_id: The payment channel identifier (URL-encoded automatically).
183
+
184
+ Returns:
185
+ Server response dict with session state and usage details.
186
+ """
187
+ data = self._http.get(f"/api/mpp/session/{quote(channel_id, safe='')}")
188
+ return data
189
+
190
+ # Convenience re-exports as static methods for discoverability
191
+ build_proxy_headers = staticmethod(build_proxy_headers)
192
+ build_session_headers = staticmethod(build_session_headers)
193
+
194
+ def __repr__(self) -> str:
195
+ return "MppResource()"
196
+
197
+
198
+ # ──────────────────────────────────────────────────────────────────────
199
+ # Async
200
+ # ──────────────────────────────────────────────────────────────────────
201
+
202
+
203
+ class AsyncMppResource:
204
+ """Asynchronous MPP (Micropayment Protocol) operations.
205
+
206
+ MPP enables keyless proxy access for AI agents: call ``get_challenge()`` to
207
+ obtain a credential, then use ``build_proxy_headers()`` to construct the proxy
208
+ Authorization header -- no API key needed.
209
+ """
210
+
211
+ def __init__(self, http: AsyncHttpClient) -> None:
212
+ self._http = http
213
+
214
+ async def get_info(self) -> dict:
215
+ """Get MPP protocol information.
216
+
217
+ This endpoint does not require authentication.
218
+ """
219
+ data = await self._http.get("/api/mpp/info", requires_auth=False)
220
+ return data
221
+
222
+ async def get_challenge(self, pool_type: str) -> dict:
223
+ """Request an MPP challenge for keyless proxy access.
224
+
225
+ This endpoint does not require authentication. The returned credential
226
+ can be used with ``build_proxy_headers()`` to access the proxy without
227
+ an API key.
228
+
229
+ Args:
230
+ pool_type: Proxy pool type -- ``"dc"`` or ``"residential"``.
231
+ """
232
+ if pool_type not in ("dc", "residential"):
233
+ raise ValueError("pool_type must be 'dc' or 'residential'")
234
+ data = await self._http.post(
235
+ "/api/mpp/challenge",
236
+ json={"poolType": pool_type},
237
+ requires_auth=False,
238
+ )
239
+ return data
240
+
241
+ async def top_up(self, amount_cents: int, method: str) -> dict:
242
+ """Top up the MPP balance.
243
+
244
+ Requires authentication (wallet credit needs a user account).
245
+
246
+ Args:
247
+ amount_cents: Amount in cents (must be positive, <= 2,147,483,647).
248
+ method: Payment method identifier (e.g. "stripe", "paypal", "crypto").
249
+ """
250
+ _validate_finite_int(amount_cents, "amount_cents")
251
+ _validate_amount_cents(amount_cents, "amount_cents")
252
+ data = await self._http.post("/api/mpp/topup", json={
253
+ "amountCents": amount_cents,
254
+ "method": method,
255
+ })
256
+ return data
257
+
258
+ async def open_session(self, max_deposit_cents: int, method: str, pool_type: str) -> dict:
259
+ """Open a new MPP payment session (payment channel).
260
+
261
+ This endpoint does not require authentication -- anonymous agents can
262
+ open sessions by providing a payment method and deposit.
263
+
264
+ Args:
265
+ max_deposit_cents: Maximum deposit in cents (must be positive, <= 2,147,483,647).
266
+ method: Payment method identifier.
267
+ pool_type: Proxy pool type (e.g. "dc", "residential").
268
+ """
269
+ _validate_finite_int(max_deposit_cents, "max_deposit_cents")
270
+ _validate_amount_cents(max_deposit_cents, "max_deposit_cents")
271
+ data = await self._http.post("/api/mpp/session/open", json={
272
+ "maxDepositCents": max_deposit_cents,
273
+ "method": method,
274
+ "poolType": pool_type,
275
+ }, requires_auth=False)
276
+ return data
277
+
278
+ async def close_session(self, channel_id: str) -> dict:
279
+ """Close an existing MPP payment session.
280
+
281
+ Args:
282
+ channel_id: The payment channel identifier.
283
+ """
284
+ data = await self._http.post("/api/mpp/session/close", json={
285
+ "channelId": channel_id,
286
+ })
287
+ return data
288
+
289
+ async def get_session(self, channel_id: str) -> dict:
290
+ """Get details of an MPP payment session.
291
+
292
+ Args:
293
+ channel_id: The payment channel identifier (URL-encoded automatically).
294
+ """
295
+ data = await self._http.get(f"/api/mpp/session/{quote(channel_id, safe='')}")
296
+ return data
297
+
298
+ # Convenience re-exports as static methods for discoverability
299
+ build_proxy_headers = staticmethod(build_proxy_headers)
300
+ build_session_headers = staticmethod(build_session_headers)
301
+
302
+ def __repr__(self) -> str:
303
+ return "AsyncMppResource()"
@@ -0,0 +1,138 @@
1
+ """x402 V2 (HTTP 402 Payment Required) protocol information resource.
2
+
3
+ V2 changes from V1:
4
+ - Protocol version "2" (was "1")
5
+ - CAIP-2 network IDs (e.g. "eip155:8453" instead of "base")
6
+ - Payment header: PAYMENT-SIGNATURE (was X-PAYMENT)
7
+ - Response header: PAYMENT-RESPONSE (was X-PAYMENT-RESPONSE)
8
+ - Requirements header: PAYMENT-REQUIRED
9
+ - New token: EURC (in addition to USDC)
10
+ - New scheme: permit2 (in addition to exact)
11
+ - Additional facilitator chains: Ethereum (eip155:1), Optimism (eip155:10),
12
+ Arbitrum (eip155:42161), and Solana
13
+ """
14
+
15
+ from __future__ import annotations
16
+
17
+ from typing import List
18
+ from dataclasses import dataclass, field
19
+
20
+ from .http_client import AsyncHttpClient, SyncHttpClient
21
+
22
+ # x402 V2 protocol version.
23
+ X402_PROTOCOL_VERSION = "2"
24
+
25
+ # x402 V2 header constants.
26
+ # V2 renamed the payment headers. Servers should accept both old and new
27
+ # for backward compatibility during the transition period.
28
+ X402_HEADER_PAYMENT_SIGNATURE = "PAYMENT-SIGNATURE"
29
+ X402_HEADER_PAYMENT_SIGNATURE_LEGACY = "X-PAYMENT"
30
+ X402_HEADER_PAYMENT_RESPONSE = "PAYMENT-RESPONSE"
31
+ X402_HEADER_PAYMENT_RESPONSE_LEGACY = "X-PAYMENT-RESPONSE"
32
+ X402_HEADER_PAYMENT_REQUIRED = "PAYMENT-REQUIRED"
33
+
34
+
35
+ @dataclass(frozen=True)
36
+ class X402Facilitator:
37
+ address: str
38
+ chain: str
39
+ """Human-readable network name (e.g. "base", "ethereum")."""
40
+ network: str
41
+ """CAIP-2 network identifier (e.g. "eip155:8453"). Added in V2."""
42
+ network_id: str = ""
43
+
44
+
45
+ @dataclass(frozen=True)
46
+ class X402Pricing:
47
+ per_request_cents: int
48
+ per_gb_cents: int
49
+ currency: str
50
+
51
+
52
+ @dataclass(frozen=True)
53
+ class X402PaymentHeaders:
54
+ """Header names used by the current protocol version."""
55
+ request: str = X402_HEADER_PAYMENT_SIGNATURE
56
+ response: str = X402_HEADER_PAYMENT_RESPONSE
57
+ required: str = X402_HEADER_PAYMENT_REQUIRED
58
+
59
+
60
+ @dataclass(frozen=True)
61
+ class X402Info:
62
+ supported: bool
63
+ enabled: bool
64
+ protocol: str
65
+ """Protocol version -- "2" for x402 V2."""
66
+ version: str
67
+ facilitators: List[X402Facilitator]
68
+ pricing: X402Pricing
69
+ """Supported token symbols (e.g. ["USDC", "EURC"])."""
70
+ currencies: List[str]
71
+ """Supported payment schemes (e.g. ["exact", "permit2"])."""
72
+ schemes: List[str] = field(default_factory=list)
73
+ wallet_type: str = ""
74
+ agentic_wallets: bool = False
75
+ """Header names used by this protocol version."""
76
+ payment_headers: X402PaymentHeaders = field(default_factory=X402PaymentHeaders)
77
+
78
+
79
+ def _parse_x402_info(data: dict) -> X402Info:
80
+ """Parse x402 info response into typed dataclass."""
81
+ facilitators = [
82
+ X402Facilitator(
83
+ address=f["address"],
84
+ chain=f["chain"],
85
+ network=f["network"],
86
+ network_id=f.get("networkId", ""),
87
+ )
88
+ for f in data.get("facilitators", [])
89
+ ]
90
+ pricing_data = data.get("pricing", {})
91
+ pricing = X402Pricing(
92
+ per_request_cents=pricing_data.get("perRequestCents", 0),
93
+ per_gb_cents=pricing_data.get("perGbCents", 0),
94
+ currency=pricing_data.get("currency", ""),
95
+ )
96
+ headers_data = data.get("paymentHeaders", {})
97
+ payment_headers = X402PaymentHeaders(
98
+ request=headers_data.get("request", X402_HEADER_PAYMENT_SIGNATURE),
99
+ response=headers_data.get("response", X402_HEADER_PAYMENT_RESPONSE),
100
+ required=headers_data.get("required", X402_HEADER_PAYMENT_REQUIRED),
101
+ )
102
+ return X402Info(
103
+ supported=data.get("supported", False),
104
+ enabled=data.get("enabled", False),
105
+ protocol=data.get("protocol", ""),
106
+ version=data.get("version", ""),
107
+ facilitators=facilitators,
108
+ pricing=pricing,
109
+ currencies=data.get("currencies", []),
110
+ schemes=data.get("schemes", []),
111
+ wallet_type=data.get("walletType", ""),
112
+ agentic_wallets=data.get("agenticWallets", False),
113
+ payment_headers=payment_headers,
114
+ )
115
+
116
+
117
+ class X402Resource:
118
+ """Synchronous x402 V2 protocol information operations."""
119
+
120
+ def __init__(self, http: SyncHttpClient) -> None:
121
+ self._http = http
122
+
123
+ def get_info(self) -> X402Info:
124
+ """Get x402 V2 protocol information (no auth required)."""
125
+ data = self._http.get("/api/x402/info", requires_auth=False)
126
+ return _parse_x402_info(data)
127
+
128
+
129
+ class AsyncX402Resource:
130
+ """Asynchronous x402 V2 protocol information operations."""
131
+
132
+ def __init__(self, http: AsyncHttpClient) -> None:
133
+ self._http = http
134
+
135
+ async def get_info(self) -> X402Info:
136
+ """Get x402 V2 protocol information (no auth required)."""
137
+ data = await self._http.get("/api/x402/info", requires_auth=False)
138
+ return _parse_x402_info(data)
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: dominusnode
3
- Version: 1.1.1
3
+ Version: 1.3.0
4
4
  Summary: Official Dominus Node SDK for Python
5
5
  License: MIT
6
6
  Requires-Python: >=3.9
@@ -10,6 +10,7 @@ dominusnode/constants.py
10
10
  dominusnode/errors.py
11
11
  dominusnode/http_client.py
12
12
  dominusnode/keys.py
13
+ dominusnode/mpp.py
13
14
  dominusnode/plans.py
14
15
  dominusnode/proxy.py
15
16
  dominusnode/py.typed
@@ -4,7 +4,7 @@ build-backend = "setuptools.build_meta"
4
4
 
5
5
  [project]
6
6
  name = "dominusnode"
7
- version = "1.1.1"
7
+ version = "1.3.0"
8
8
  description = "Official Dominus Node SDK for Python"
9
9
  requires-python = ">=3.9"
10
10
  license = {text = "MIT"}
@@ -1,88 +0,0 @@
1
- """x402 (HTTP 402 Payment Required) protocol information resource."""
2
-
3
- from __future__ import annotations
4
-
5
- from typing import List
6
- from dataclasses import dataclass, field
7
-
8
- from .http_client import AsyncHttpClient, SyncHttpClient
9
-
10
-
11
- @dataclass(frozen=True)
12
- class X402Facilitator:
13
- address: str
14
- chain: str
15
- network: str
16
-
17
-
18
- @dataclass(frozen=True)
19
- class X402Pricing:
20
- per_request_cents: int
21
- per_gb_cents: int
22
- currency: str
23
-
24
-
25
- @dataclass(frozen=True)
26
- class X402Info:
27
- supported: bool
28
- enabled: bool
29
- protocol: str
30
- version: str
31
- facilitators: List[X402Facilitator]
32
- pricing: X402Pricing
33
- currencies: List[str]
34
- wallet_type: str
35
- agentic_wallets: bool
36
-
37
-
38
- def _parse_x402_info(data: dict) -> X402Info:
39
- """Parse x402 info response into typed dataclass."""
40
- facilitators = [
41
- X402Facilitator(
42
- address=f["address"],
43
- chain=f["chain"],
44
- network=f["network"],
45
- )
46
- for f in data.get("facilitators", [])
47
- ]
48
- pricing_data = data.get("pricing", {})
49
- pricing = X402Pricing(
50
- per_request_cents=pricing_data.get("perRequestCents", 0),
51
- per_gb_cents=pricing_data.get("perGbCents", 0),
52
- currency=pricing_data.get("currency", ""),
53
- )
54
- return X402Info(
55
- supported=data.get("supported", False),
56
- enabled=data.get("enabled", False),
57
- protocol=data.get("protocol", ""),
58
- version=data.get("version", ""),
59
- facilitators=facilitators,
60
- pricing=pricing,
61
- currencies=data.get("currencies", []),
62
- wallet_type=data.get("walletType", ""),
63
- agentic_wallets=data.get("agenticWallets", False),
64
- )
65
-
66
-
67
- class X402Resource:
68
- """Synchronous x402 protocol information operations."""
69
-
70
- def __init__(self, http: SyncHttpClient) -> None:
71
- self._http = http
72
-
73
- def get_info(self) -> X402Info:
74
- """Get x402 protocol information (no auth required)."""
75
- data = self._http.get("/api/x402/info", requires_auth=False)
76
- return _parse_x402_info(data)
77
-
78
-
79
- class AsyncX402Resource:
80
- """Asynchronous x402 protocol information operations."""
81
-
82
- def __init__(self, http: AsyncHttpClient) -> None:
83
- self._http = http
84
-
85
- async def get_info(self) -> X402Info:
86
- """Get x402 protocol information (no auth required)."""
87
- data = await self._http.get("/api/x402/info", requires_auth=False)
88
- return _parse_x402_info(data)
File without changes
File without changes