atp-protocol 1.0.1__py3-none-any.whl → 1.3.0__py3-none-any.whl

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.
atp/__init__.py CHANGED
@@ -1,8 +1,16 @@
1
+ from atp.client import ATPClient
1
2
  from atp.middleware import ATPSettlementMiddleware, create_settlement_middleware
2
- from atp.settlement_client import SettlementServiceClient
3
+ from atp.schemas import ATPSettlementMiddlewareConfig
4
+ from atp.settlement_client import (
5
+ SettlementServiceClient,
6
+ SettlementServiceError,
7
+ )
3
8
 
4
9
  __all__ = [
10
+ "ATPClient",
5
11
  "ATPSettlementMiddleware",
6
12
  "create_settlement_middleware",
13
+ "ATPSettlementMiddlewareConfig",
7
14
  "SettlementServiceClient",
15
+ "SettlementServiceError",
8
16
  ]
atp/client.py ADDED
@@ -0,0 +1,618 @@
1
+ """
2
+ User-facing client API for ATP Protocol.
3
+
4
+ This module provides a simple, high-level interface for:
5
+ 1. Calling the facilitator (settlement service) directly
6
+ 2. Making requests to services protected by ATP middleware
7
+ 3. Handling wallet authentication and settlement automatically
8
+ """
9
+
10
+ from __future__ import annotations
11
+
12
+ import json
13
+ import traceback
14
+ from typing import Any, Dict, Optional, Union
15
+
16
+ import httpx
17
+
18
+ from atp.config import ATP_SETTLEMENT_URL, ATP_SETTLEMENT_TIMEOUT
19
+ from atp.encryption import ResponseEncryptor
20
+ from atp.schemas import PaymentToken
21
+ from atp.settlement_client import (
22
+ SettlementServiceClient,
23
+ )
24
+
25
+ from loguru import logger
26
+
27
+ class ATPClient:
28
+ """
29
+ User-facing client for ATP Protocol.
30
+
31
+ This client provides a simple interface for:
32
+ - Calling the facilitator (settlement service) for payment operations
33
+ - Making requests to ATP-protected endpoints with automatic wallet handling
34
+ - Parsing usage data and calculating payments
35
+ - Handling encrypted responses from ATP middleware
36
+
37
+ **Example Usage:**
38
+
39
+ ```python
40
+ from atp.client import ATPClient
41
+ from atp.schemas import PaymentToken
42
+
43
+ # Initialize client with wallet
44
+ client = ATPClient(
45
+ wallet_private_key="[1,2,3,...]", # Your wallet private key
46
+ settlement_service_url="https://facilitator.swarms.world"
47
+ )
48
+
49
+ # Call facilitator directly
50
+ usage = {"input_tokens": 1000, "output_tokens": 500}
51
+ payment = await client.calculate_payment(
52
+ usage=usage,
53
+ input_cost_per_million_usd=10.0,
54
+ output_cost_per_million_usd=30.0,
55
+ payment_token=PaymentToken.SOL
56
+ )
57
+
58
+ # Execute settlement
59
+ result = await client.settle(
60
+ usage=usage,
61
+ input_cost_per_million_usd=10.0,
62
+ output_cost_per_million_usd=30.0,
63
+ recipient_pubkey="RecipientPublicKeyHere",
64
+ payment_token=PaymentToken.SOL
65
+ )
66
+
67
+ # Make request to ATP-protected endpoint
68
+ response = await client.request(
69
+ method="POST",
70
+ url="https://api.example.com/v1/chat",
71
+ json={"message": "Hello!"}
72
+ )
73
+ # Wallet is automatically included in headers
74
+ # Response is automatically decrypted if encrypted
75
+ ```
76
+
77
+ **Attributes:**
78
+ wallet_private_key (str): Wallet private key for authentication and payments.
79
+ settlement_service_url (str): Base URL of the settlement service.
80
+ settlement_timeout (float): Timeout for settlement operations in seconds.
81
+ wallet_private_key_header (str): HTTP header name for wallet key (default: "x-wallet-private-key").
82
+ settlement_client (SettlementServiceClient): Internal client for settlement service.
83
+ encryptor (ResponseEncryptor): Encryptor for handling encrypted responses.
84
+ """
85
+
86
+ def __init__(
87
+ self,
88
+ wallet_private_key: Optional[str] = None,
89
+ settlement_service_url: Optional[str] = ATP_SETTLEMENT_URL,
90
+ settlement_timeout: Optional[float] = None,
91
+ wallet_private_key_header: str = "x-wallet-private-key",
92
+ verbose: bool = False,
93
+ ):
94
+ """
95
+ Initialize the ATP client.
96
+
97
+ Args:
98
+ wallet_private_key: Wallet private key for authentication and payments.
99
+ Can be in JSON array format (e.g., "[1,2,3,...]") or base58 string.
100
+ If not provided, must be passed per-request.
101
+ settlement_service_url: Base URL of the settlement service.
102
+ Default: from ATP_SETTLEMENT_URL env var or "https://facilitator.swarms.world".
103
+ settlement_timeout: Timeout for settlement operations in seconds.
104
+ Default: from ATP_SETTLEMENT_TIMEOUT env var or 300.0 (5 minutes).
105
+ wallet_private_key_header: HTTP header name for wallet private key.
106
+ Default: "x-wallet-private-key".
107
+ verbose: If True, enables detailed logging with tracebacks for debugging.
108
+ Default: False.
109
+ """
110
+ self.wallet_private_key = wallet_private_key
111
+ self.wallet_private_key_header = wallet_private_key_header
112
+ self.settlement_service_url = (
113
+ settlement_service_url or ATP_SETTLEMENT_URL
114
+ )
115
+ self.settlement_timeout = (
116
+ settlement_timeout if settlement_timeout is not None else ATP_SETTLEMENT_TIMEOUT
117
+ )
118
+ self.verbose = verbose
119
+
120
+ # Initialize settlement service client
121
+ self.settlement_client = SettlementServiceClient(
122
+ base_url=self.settlement_service_url,
123
+ timeout=self.settlement_timeout,
124
+ )
125
+
126
+ # Initialize encryptor for handling encrypted responses
127
+ self.encryptor = ResponseEncryptor()
128
+
129
+ if self.verbose:
130
+ logger.info(f"ATPClient initialized with settlement_service_url={self.settlement_service_url}, timeout={self.settlement_timeout}")
131
+
132
+ def _get_headers(
133
+ self, wallet_private_key: Optional[str] = None, **kwargs
134
+ ) -> Dict[str, str]:
135
+ """
136
+ Get HTTP headers with wallet authentication.
137
+
138
+ Args:
139
+ wallet_private_key: Wallet private key to use. If not provided,
140
+ uses the client's default wallet_private_key.
141
+ **kwargs: Additional headers to include.
142
+
143
+ Returns:
144
+ Dict of HTTP headers.
145
+ """
146
+ headers = {"Content-Type": "application/json", **kwargs}
147
+
148
+ # Add wallet private key if available
149
+ key = wallet_private_key or self.wallet_private_key
150
+ if key:
151
+ headers[self.wallet_private_key_header] = key
152
+
153
+ return headers
154
+
155
+ async def parse_usage(
156
+ self, usage_data: Dict[str, Any]
157
+ ) -> Dict[str, Optional[int]]:
158
+ """
159
+ Parse usage tokens from various API formats.
160
+
161
+ This method uses the facilitator to parse usage data from any supported
162
+ format (OpenAI, Anthropic, Google, etc.) and normalize it to a standard format.
163
+
164
+ Args:
165
+ usage_data: Usage data in any supported format. Can be the entire
166
+ response body or just the usage portion.
167
+
168
+ Returns:
169
+ Dict with normalized keys:
170
+ - `input_tokens` (Optional[int]): Number of input/prompt tokens
171
+ - `output_tokens` (Optional[int]): Number of output/completion tokens
172
+ - `total_tokens` (Optional[int]): Total number of tokens
173
+
174
+ Raises:
175
+ SettlementServiceError: If the facilitator returns an error.
176
+
177
+ Example:
178
+ ```python
179
+ usage = await client.parse_usage({
180
+ "prompt_tokens": 100,
181
+ "completion_tokens": 50,
182
+ "total_tokens": 150
183
+ })
184
+ # Returns: {"input_tokens": 100, "output_tokens": 50, "total_tokens": 150}
185
+ ```
186
+ """
187
+ if self.verbose:
188
+ logger.debug(f"Parsing usage data: {usage_data}")
189
+
190
+ try:
191
+ result = await self.settlement_client.parse_usage(usage_data)
192
+ if self.verbose:
193
+ logger.info(f"Successfully parsed usage: {result}")
194
+ return result
195
+ except Exception as e:
196
+ if self.verbose:
197
+ logger.error(f"Error parsing usage data: {e}\n{traceback.format_exc()}")
198
+ else:
199
+ logger.error(f"Error parsing usage data: {e}")
200
+ raise
201
+
202
+ async def calculate_payment(
203
+ self,
204
+ usage: Dict[str, Any],
205
+ input_cost_per_million_usd: float,
206
+ output_cost_per_million_usd: float,
207
+ payment_token: Union[PaymentToken, str] = PaymentToken.SOL,
208
+ ) -> Dict[str, Any]:
209
+ """
210
+ Calculate payment amounts from usage data.
211
+
212
+ This method uses the facilitator to calculate payment amounts based on
213
+ token usage and pricing rates. It does not execute any payment.
214
+
215
+ Args:
216
+ usage: Usage data containing token counts. Supports same formats as
217
+ `parse_usage` method.
218
+ input_cost_per_million_usd: Cost per million input tokens in USD.
219
+ output_cost_per_million_usd: Cost per million output tokens in USD.
220
+ payment_token: Token to use for payment. Must be "SOL" or "USDC".
221
+ Default: PaymentToken.SOL.
222
+
223
+ Returns:
224
+ Dict with payment calculation details:
225
+ - `status` (str): "calculated" or "skipped" (if zero cost)
226
+ - `pricing` (dict): Pricing information with token counts and costs
227
+ - `payment_amounts` (dict, optional): Payment amounts in token units
228
+ - `token_price_usd` (float, optional): Current token price in USD
229
+
230
+ Raises:
231
+ SettlementServiceError: If the facilitator returns an error.
232
+
233
+ Example:
234
+ ```python
235
+ result = await client.calculate_payment(
236
+ usage={"input_tokens": 1000, "output_tokens": 500},
237
+ input_cost_per_million_usd=10.0,
238
+ output_cost_per_million_usd=30.0,
239
+ payment_token=PaymentToken.SOL
240
+ )
241
+ ```
242
+ """
243
+ payment_token_str = (
244
+ payment_token.value if isinstance(payment_token, PaymentToken) else payment_token
245
+ )
246
+
247
+ if self.verbose:
248
+ logger.debug(
249
+ f"Calculating payment: usage={usage}, "
250
+ f"input_cost_per_million_usd={input_cost_per_million_usd}, "
251
+ f"output_cost_per_million_usd={output_cost_per_million_usd}, "
252
+ f"payment_token={payment_token_str}"
253
+ )
254
+
255
+ try:
256
+ result = await self.settlement_client.calculate_payment(
257
+ usage=usage,
258
+ input_cost_per_million_usd=input_cost_per_million_usd,
259
+ output_cost_per_million_usd=output_cost_per_million_usd,
260
+ payment_token=payment_token_str,
261
+ )
262
+ if self.verbose:
263
+ logger.info(f"Payment calculation successful: {result}")
264
+ return result
265
+ except Exception as e:
266
+ if self.verbose:
267
+ logger.error(f"Error calculating payment: {e}\n{traceback.format_exc()}")
268
+ else:
269
+ logger.error(f"Error calculating payment: {e}")
270
+ raise
271
+
272
+ async def settle(
273
+ self,
274
+ usage: Dict[str, Any],
275
+ input_cost_per_million_usd: float,
276
+ output_cost_per_million_usd: float,
277
+ recipient_pubkey: str,
278
+ payment_token: Union[PaymentToken, str] = PaymentToken.SOL,
279
+ skip_preflight: bool = False,
280
+ commitment: str = "confirmed",
281
+ wallet_private_key: Optional[str] = None,
282
+ ) -> Dict[str, Any]:
283
+ """
284
+ Execute a settlement payment on Solana blockchain.
285
+
286
+ This method uses the facilitator to execute a complete settlement:
287
+ parse usage, calculate payment, fetch token prices, create and sign
288
+ transaction, send to Solana, and wait for confirmation.
289
+
290
+ Args:
291
+ usage: Usage data containing token counts. Supports same formats as
292
+ `parse_usage` method.
293
+ input_cost_per_million_usd: Cost per million input tokens in USD.
294
+ output_cost_per_million_usd: Cost per million output tokens in USD.
295
+ recipient_pubkey: Solana public key of the recipient wallet (base58 encoded).
296
+ This wallet receives the net payment after fees.
297
+ payment_token: Token to use for payment. Currently only "SOL" is supported
298
+ for automatic settlement. Default: PaymentToken.SOL.
299
+ skip_preflight: Whether to skip preflight simulation. Default: False.
300
+ commitment: Solana commitment level for transaction confirmation:
301
+ - "processed": Fastest, but may be rolled back
302
+ - "confirmed": Recommended default, confirmed by cluster
303
+ - "finalized": Slowest, but cannot be rolled back
304
+ Default: "confirmed".
305
+ wallet_private_key: Wallet private key to use for payment. If not provided,
306
+ uses the client's default wallet_private_key.
307
+
308
+ Returns:
309
+ Dict with payment details:
310
+ - `status` (str): "paid" if successful, "skipped" if zero cost
311
+ - `transaction_signature` (str, optional): Solana transaction signature
312
+ - `pricing` (dict): Complete cost breakdown
313
+ - `payment` (dict, optional): Payment details including amounts and splits
314
+
315
+ Raises:
316
+ SettlementServiceError: If the facilitator returns an error.
317
+ ValueError: If wallet_private_key is not provided.
318
+
319
+ Example:
320
+ ```python
321
+ result = await client.settle(
322
+ usage={"input_tokens": 1000, "output_tokens": 500},
323
+ input_cost_per_million_usd=10.0,
324
+ output_cost_per_million_usd=30.0,
325
+ recipient_pubkey="RecipientPublicKeyHere",
326
+ payment_token=PaymentToken.SOL
327
+ )
328
+ ```
329
+ """
330
+ private_key = wallet_private_key or self.wallet_private_key
331
+ if not private_key:
332
+ error_msg = (
333
+ "wallet_private_key must be provided either in client initialization "
334
+ "or as a parameter to this method"
335
+ )
336
+ if self.verbose:
337
+ logger.error(f"{error_msg}\n{traceback.format_exc()}")
338
+ else:
339
+ logger.error(error_msg)
340
+ raise ValueError(error_msg)
341
+
342
+ payment_token_str = (
343
+ payment_token.value if isinstance(payment_token, PaymentToken) else payment_token
344
+ )
345
+
346
+ if self.verbose:
347
+ logger.debug(
348
+ f"Settling payment: usage={usage}, "
349
+ f"input_cost_per_million_usd={input_cost_per_million_usd}, "
350
+ f"output_cost_per_million_usd={output_cost_per_million_usd}, "
351
+ f"recipient_pubkey={recipient_pubkey}, "
352
+ f"payment_token={payment_token_str}, "
353
+ f"skip_preflight={skip_preflight}, "
354
+ f"commitment={commitment}"
355
+ )
356
+
357
+ try:
358
+ result = await self.settlement_client.settle(
359
+ private_key=private_key,
360
+ usage=usage,
361
+ input_cost_per_million_usd=input_cost_per_million_usd,
362
+ output_cost_per_million_usd=output_cost_per_million_usd,
363
+ recipient_pubkey=recipient_pubkey,
364
+ payment_token=payment_token_str,
365
+ skip_preflight=skip_preflight,
366
+ commitment=commitment,
367
+ )
368
+ if self.verbose:
369
+ logger.info(f"Settlement successful: {result}")
370
+ return result
371
+ except Exception as e:
372
+ if self.verbose:
373
+ logger.error(f"Error during settlement: {e}\n{traceback.format_exc()}")
374
+ else:
375
+ logger.error(f"Error during settlement: {e}")
376
+ raise
377
+
378
+ async def health_check(self) -> Dict[str, Any]:
379
+ """
380
+ Check if the facilitator (settlement service) is healthy.
381
+
382
+ Returns:
383
+ Dict with health status information, typically including:
384
+ - `status` (str): Service status (e.g., "healthy")
385
+ - `service` (str): Service name
386
+ - `version` (str): Service version
387
+
388
+ Raises:
389
+ SettlementServiceError: If the facilitator is unreachable or returns an error.
390
+
391
+ Example:
392
+ ```python
393
+ health = await client.health_check()
394
+ print(f"Service status: {health['status']}")
395
+ ```
396
+ """
397
+ if self.verbose:
398
+ logger.debug("Checking settlement service health")
399
+
400
+ try:
401
+ result = await self.settlement_client.health_check()
402
+ if self.verbose:
403
+ logger.info(f"Health check successful: {result}")
404
+ return result
405
+ except Exception as e:
406
+ if self.verbose:
407
+ logger.error(f"Error during health check: {e}\n{traceback.format_exc()}")
408
+ else:
409
+ logger.error(f"Error during health check: {e}")
410
+ raise
411
+
412
+ async def request(
413
+ self,
414
+ method: str,
415
+ url: str,
416
+ wallet_private_key: Optional[str] = None,
417
+ auto_decrypt: bool = True,
418
+ **kwargs,
419
+ ) -> Dict[str, Any]:
420
+ """
421
+ Make an HTTP request to an ATP-protected endpoint.
422
+
423
+ This method automatically:
424
+ - Adds wallet authentication headers
425
+ - Handles encrypted responses from ATP middleware
426
+ - Decrypts response data if encrypted
427
+
428
+ Args:
429
+ method: HTTP method (GET, POST, PUT, DELETE, etc.).
430
+ url: Full URL of the endpoint.
431
+ wallet_private_key: Wallet private key to use. If not provided,
432
+ uses the client's default wallet_private_key.
433
+ auto_decrypt: Whether to automatically decrypt encrypted responses.
434
+ Default: True.
435
+ **kwargs: Additional arguments to pass to httpx (e.g., json, data, params, etc.).
436
+
437
+ Returns:
438
+ Dict containing the response data. If the response was encrypted,
439
+ it will be automatically decrypted if auto_decrypt=True.
440
+
441
+ Raises:
442
+ httpx.HTTPError: If the HTTP request fails.
443
+ ValueError: If wallet_private_key is required but not provided.
444
+
445
+ Example:
446
+ ```python
447
+ # Make a POST request to an ATP-protected endpoint
448
+ response = await client.request(
449
+ method="POST",
450
+ url="https://api.example.com/v1/chat",
451
+ json={"message": "Hello!"}
452
+ )
453
+ # Response is automatically decrypted if encrypted
454
+ print(response["output"]) # Agent output
455
+ print(response["atp_settlement"]) # Payment details
456
+ ```
457
+ """
458
+ if self.verbose:
459
+ logger.debug(f"Making {method} request to {url}")
460
+
461
+ try:
462
+ # Check if wallet key is available
463
+ key = wallet_private_key or self.wallet_private_key
464
+ if not key:
465
+ raise ValueError(
466
+ "wallet_private_key is required for ATP-protected endpoints. "
467
+ "Provide it in client initialization or pass it to the request method. "
468
+ "Example: client = ATPClient(wallet_private_key='[1,2,3,...]')"
469
+ )
470
+
471
+ # Get headers with wallet authentication
472
+ headers = self._get_headers(wallet_private_key=wallet_private_key)
473
+
474
+ # Merge with any headers provided in kwargs
475
+ if "headers" in kwargs:
476
+ headers.update(kwargs.pop("headers"))
477
+
478
+ if self.verbose:
479
+ logger.debug(f"Request headers: {list(headers.keys())}")
480
+
481
+ # Make the request
482
+ async with httpx.AsyncClient(timeout=self.settlement_timeout) as client:
483
+ response = await client.request(
484
+ method=method,
485
+ url=url,
486
+ headers=headers,
487
+ **kwargs,
488
+ )
489
+ response.raise_for_status()
490
+
491
+ if self.verbose:
492
+ logger.debug(f"Response status: {response.status_code}")
493
+
494
+ # Parse JSON response
495
+ try:
496
+ response_data = response.json()
497
+ except json.JSONDecodeError as e:
498
+ if self.verbose:
499
+ logger.warning(f"Response is not JSON, returning as text: {e}")
500
+ # If not JSON, return text
501
+ return {"text": response.text}
502
+
503
+ # Auto-decrypt if enabled and response appears encrypted
504
+ if auto_decrypt:
505
+ if self.verbose:
506
+ logger.debug("Attempting to decrypt response")
507
+ response_data = self.encryptor.decrypt_response_data(
508
+ response_data
509
+ )
510
+
511
+ if self.verbose:
512
+ logger.info(f"Request successful: {method} {url}")
513
+
514
+ return response_data
515
+ except httpx.HTTPError as e:
516
+ if self.verbose:
517
+ logger.error(f"HTTP error during request {method} {url}: {e}\n{traceback.format_exc()}")
518
+ else:
519
+ logger.error(f"HTTP error during request {method} {url}: {e}")
520
+ raise
521
+ except Exception as e:
522
+ if self.verbose:
523
+ logger.error(f"Error during request {method} {url}: {e}\n{traceback.format_exc()}")
524
+ else:
525
+ logger.error(f"Error during request {method} {url}: {e}")
526
+ raise
527
+
528
+ async def post(
529
+ self,
530
+ url: str,
531
+ wallet_private_key: Optional[str] = None,
532
+ auto_decrypt: bool = True,
533
+ **kwargs,
534
+ ) -> Dict[str, Any]:
535
+ """
536
+ Make a POST request to an ATP-protected endpoint.
537
+
538
+ Convenience method for POST requests. See `request` method for details.
539
+
540
+ Args:
541
+ url: Full URL of the endpoint.
542
+ wallet_private_key: Wallet private key to use. If not provided,
543
+ uses the client's default wallet_private_key.
544
+ auto_decrypt: Whether to automatically decrypt encrypted responses.
545
+ Default: True.
546
+ **kwargs: Additional arguments to pass to httpx (e.g., json, data, params, etc.).
547
+
548
+ Returns:
549
+ Dict containing the response data.
550
+
551
+ Example:
552
+ ```python
553
+ response = await client.post(
554
+ url="https://api.example.com/v1/chat",
555
+ json={"message": "Hello!"}
556
+ )
557
+ ```
558
+ """
559
+ try:
560
+ return await self.request(
561
+ method="POST",
562
+ url=url,
563
+ wallet_private_key=wallet_private_key,
564
+ auto_decrypt=auto_decrypt,
565
+ **kwargs,
566
+ )
567
+ except Exception as e:
568
+ if self.verbose:
569
+ logger.error(f"Error in POST request to {url}: {e}\n{traceback.format_exc()}")
570
+ else:
571
+ logger.error(f"Error in POST request to {url}: {e}")
572
+ raise
573
+
574
+ async def get(
575
+ self,
576
+ url: str,
577
+ wallet_private_key: Optional[str] = None,
578
+ auto_decrypt: bool = True,
579
+ **kwargs,
580
+ ) -> Dict[str, Any]:
581
+ """
582
+ Make a GET request to an ATP-protected endpoint.
583
+
584
+ Convenience method for GET requests. See `request` method for details.
585
+
586
+ Args:
587
+ url: Full URL of the endpoint.
588
+ wallet_private_key: Wallet private key to use. If not provided,
589
+ uses the client's default wallet_private_key.
590
+ auto_decrypt: Whether to automatically decrypt encrypted responses.
591
+ Default: True.
592
+ **kwargs: Additional arguments to pass to httpx (e.g., params, headers, etc.).
593
+
594
+ Returns:
595
+ Dict containing the response data.
596
+
597
+ Example:
598
+ ```python
599
+ response = await client.get(
600
+ url="https://api.example.com/v1/status",
601
+ params={"id": "123"}
602
+ )
603
+ ```
604
+ """
605
+ try:
606
+ return await self.request(
607
+ method="GET",
608
+ url=url,
609
+ wallet_private_key=wallet_private_key,
610
+ auto_decrypt=auto_decrypt,
611
+ **kwargs,
612
+ )
613
+ except Exception as e:
614
+ if self.verbose:
615
+ logger.error(f"Error in GET request to {url}: {e}\n{traceback.format_exc()}")
616
+ else:
617
+ logger.error(f"Error in GET request to {url}: {e}")
618
+ raise
atp/config.py CHANGED
@@ -75,3 +75,8 @@ ATP_SOLANA_DEBUG = _bool_env("ATP_SOLANA_DEBUG", default=False)
75
75
  ATP_SETTLEMENT_URL = os.getenv(
76
76
  "ATP_SETTLEMENT_URL", "https://facilitator.swarms.world"
77
77
  )
78
+
79
+ # Settlement Service Timeout (in seconds)
80
+ # Settlement operations can take longer due to blockchain confirmation times
81
+ # Default: 300 seconds (5 minutes) - can be overridden via environment variable or middleware parameter
82
+ ATP_SETTLEMENT_TIMEOUT = _float_env("ATP_SETTLEMENT_TIMEOUT") or 300.0