atp-protocol 1.0.0__py3-none-any.whl → 1.2.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,11 +1,8 @@
1
- """ATP Protocol package."""
2
-
3
- from atp.middleware import (
4
- ATPSettlementMiddleware,
5
- create_settlement_middleware,
6
- )
1
+ from atp.middleware import ATPSettlementMiddleware, create_settlement_middleware
2
+ from atp.settlement_client import SettlementServiceClient
7
3
 
8
4
  __all__ = [
9
5
  "ATPSettlementMiddleware",
10
6
  "create_settlement_middleware",
11
- ]
7
+ "SettlementServiceClient",
8
+ ]
atp/config.py CHANGED
@@ -73,5 +73,5 @@ ATP_SOLANA_DEBUG = _bool_env("ATP_SOLANA_DEBUG", default=False)
73
73
 
74
74
  # Settlement Service URL
75
75
  ATP_SETTLEMENT_URL = os.getenv(
76
- "ATP_SETTLEMENT_URL", "http://localhost:8001"
76
+ "ATP_SETTLEMENT_URL", "https://facilitator.swarms.world"
77
77
  )
atp/middleware.py CHANGED
@@ -67,8 +67,6 @@ class ATPSettlementMiddleware(BaseHTTPMiddleware):
67
67
  recipient_pubkey: Optional[str] = None,
68
68
  skip_preflight: bool = False,
69
69
  commitment: str = "confirmed",
70
- usage_response_key: str = "usage",
71
- include_usage_in_response: bool = True,
72
70
  require_wallet: bool = True,
73
71
  settlement_service_url: Optional[str] = None,
74
72
  ):
@@ -92,8 +90,6 @@ class ATPSettlementMiddleware(BaseHTTPMiddleware):
92
90
  This wallet receives the main payment (after processing fee). Required.
93
91
  skip_preflight: Whether to skip preflight simulation for Solana transactions.
94
92
  commitment: Solana commitment level (processed|confirmed|finalized).
95
- usage_response_key: Key in response JSON where usage data is located (default: "usage").
96
- include_usage_in_response: Whether to add usage/cost info to the response.
97
93
  require_wallet: Whether to require wallet private key (if False, skips settlement when missing).
98
94
  settlement_service_url: Base URL of the settlement service. If not provided, uses
99
95
  ATP_SETTLEMENT_URL environment variable (default: http://localhost:8001).
@@ -119,8 +115,6 @@ class ATPSettlementMiddleware(BaseHTTPMiddleware):
119
115
  )
120
116
  self.skip_preflight = skip_preflight
121
117
  self.commitment = commitment
122
- self.usage_response_key = usage_response_key
123
- self.include_usage_in_response = include_usage_in_response
124
118
  self.require_wallet = require_wallet
125
119
  # Always use settlement service - initialize client with config value or provided URL
126
120
  service_url = (
@@ -146,10 +140,9 @@ class ATPSettlementMiddleware(BaseHTTPMiddleware):
146
140
  """
147
141
  Extract usage information from response body.
148
142
 
149
- Tries multiple strategies:
150
- 1. Look for usage data at the configured usage_response_key
151
- 2. Check if the entire response contains usage-like keys
152
- 3. Try nested structures (usage.usage, meta.usage, etc.)
143
+ Automatically detects usage data using multiple strategies:
144
+ 1. Check if the entire response contains usage-like keys
145
+ 2. Try nested structures with common usage key names (usage, token_usage, etc.)
153
146
 
154
147
  The usage data is then sent to the settlement service for parsing,
155
148
  so we just need to extract the raw usage object.
@@ -160,12 +153,7 @@ class ATPSettlementMiddleware(BaseHTTPMiddleware):
160
153
  return None
161
154
  data = json.loads(body_str)
162
155
 
163
- # Strategy 1: Try the configured usage key first
164
- usage = data.get(self.usage_response_key)
165
- if usage and isinstance(usage, dict):
166
- return usage
167
-
168
- # Strategy 2: Check if the entire response is usage-like
156
+ # Strategy 1: Check if the entire response is usage-like
169
157
  if isinstance(data, dict):
170
158
  # Check for common usage keys at top level
171
159
  usage_keys = [
@@ -182,7 +170,7 @@ class ATPSettlementMiddleware(BaseHTTPMiddleware):
182
170
  if any(key in data for key in usage_keys):
183
171
  return data
184
172
 
185
- # Strategy 3: Try nested structures
173
+ # Strategy 2: Try nested structures
186
174
  # Check for usage nested in common locations
187
175
  for nested_key in [
188
176
  "usage",
@@ -284,21 +272,20 @@ class ATPSettlementMiddleware(BaseHTTPMiddleware):
284
272
  detail=f"Settlement failed: {str(e)}",
285
273
  )
286
274
 
287
- # Modify response to include usage/payment info if requested
288
- if self.include_usage_in_response:
289
- try:
290
- response_data = json.loads(
291
- response_body.decode("utf-8")
292
- )
293
- response_data["atp_settlement"] = payment_result
294
- response_data["atp_usage"] = usage
295
- response_body = json.dumps(response_data).encode(
296
- "utf-8"
297
- )
298
- except Exception as e:
299
- logger.warning(
300
- f"Failed to add settlement info to response: {e}"
301
- )
275
+ # Always include settlement information in the response
276
+ try:
277
+ response_data = json.loads(
278
+ response_body.decode("utf-8")
279
+ )
280
+ response_data["atp_settlement"] = payment_result
281
+ response_data["atp_usage"] = usage
282
+ response_body = json.dumps(response_data).encode(
283
+ "utf-8"
284
+ )
285
+ except Exception as e:
286
+ logger.warning(
287
+ f"Failed to add settlement info to response: {e}"
288
+ )
302
289
 
303
290
  return Response(
304
291
  content=response_body,
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.3
2
2
  Name: atp-protocol
3
- Version: 1.0.0
3
+ Version: 1.2.0
4
4
  Summary: ATP Protocol - Agentic Trade Protocol. The easiest way to enable agent-to-agent payments powered by Solana.
5
5
  License: MIT
6
6
  Keywords: atp,atp-protocol,solana,blockchain,cryptocurrency,payment-gated,agent-to-agent,agent payments,agent execution,payment settlement,solana payments,usdc,sol,fastapi,agent api,payment gateway,settlement service,agent marketplace,decentralized payments,web3,crypto payments,agent infrastructure,payment middleware,automated settlement
@@ -17,13 +17,14 @@ Classifier: Programming Language :: Python :: 3.13
17
17
  Classifier: Topic :: Internet :: WWW/HTTP :: HTTP Servers
18
18
  Classifier: Topic :: Office/Business :: Financial
19
19
  Classifier: Topic :: Software Development :: Libraries :: Python Modules
20
+ Requires-Dist: fastapi
20
21
  Requires-Dist: httpx
21
22
  Requires-Dist: loguru
22
23
  Requires-Dist: pydantic
23
24
  Requires-Dist: python-dotenv
24
25
  Requires-Dist: setuptools
25
- Requires-Dist: solana
26
- Requires-Dist: solders
26
+ Requires-Dist: starlette
27
+ Requires-Dist: swarms
27
28
  Project-URL: Documentation, https://github.com/The-Swarm-Corporation/ATP-Protocol
28
29
  Project-URL: Homepage, https://github.com/The-Swarm-Corporation/ATP-Protocol
29
30
  Project-URL: Repository, https://github.com/The-Swarm-Corporation/ATP-Protocol
@@ -254,33 +255,46 @@ stateDiagram-v2
254
255
 
255
256
  ## ATP Settlement Middleware
256
257
 
257
- The ATP Protocol also provides a **FastAPI middleware** that enables **automatic payment deduction** for any configured endpoint. Unlike the main ATP Gateway (which uses a 402 challenge pattern), the middleware automatically deducts payment **after** the endpoint executes successfully, making it ideal for APIs that want seamless, automatic settlement.
258
+ The ATP Settlement Middleware enables **automatic payment processing** for any FastAPI endpoint. Unlike the main ATP Gateway (which uses a 402 challenge), the middleware handles payment **automatically after** your endpoint executes—perfect for APIs that want seamless billing.
258
259
 
259
- ### How the Middleware Works
260
+ ### How It Works
260
261
 
261
- The `ATPSettlementMiddleware` intercepts requests to configured endpoints and:
262
+ The middleware intercepts requests, executes your endpoint, then automatically processes payment:
262
263
 
263
- 1. **Extracts wallet private key** from request headers (default: `x-wallet-private-key`)
264
- 2. **Executes the endpoint** normally (passes request through)
265
- 3. **Extracts usage data** from the response (token counts from various API formats)
266
- 4. **Calculates cost** based on configured rates (`input_cost_per_million_usd`, `output_cost_per_million_usd`)
267
- 5. **Automatically deducts payment** from the provided wallet via Solana transaction
268
- 6. **Splits payment** between recipient (endpoint host) and Swarms Treasury (5% fee)
269
- 7. **Adds settlement info** to the response (optional)
264
+ ```mermaid
265
+ sequenceDiagram
266
+ autonumber
267
+ participant C as Client
268
+ participant M as Middleware
269
+ participant E as Endpoint
270
+ participant SS as Settlement Service
271
+ participant S as Solana
270
272
 
271
- ### Key Differences from Main ATP Protocol
273
+ C->>M: POST /v1/chat<br/>(x-wallet-private-key header)
274
+ M->>E: Forward request
275
+ E-->>M: Response + usage data
276
+ M->>M: Extract token usage<br/>(auto-detects format)
277
+ M->>SS: Calculate payment<br/>(usage → USD → SOL/USDC)
278
+ SS->>SS: Split payment<br/>(95% recipient, 5% treasury)
279
+ SS->>S: Send payment transaction
280
+ S-->>SS: Transaction signature
281
+ SS-->>M: Settlement details
282
+ M->>M: Add settlement info<br/>to response
283
+ M-->>C: Response + atp_settlement
284
+ ```
272
285
 
273
- | Feature | Main ATP Gateway | Middleware |
274
- |---------|-----------------|------------|
275
- | **Payment Flow** | Two-step: 402 challenge → settle | Automatic: single request |
276
- | **Response** | Returns 402 with payment challenge | Returns normal response + settlement info |
277
- | **Integration** | Requires two API calls | Single API call with wallet header |
278
- | **Use Case** | Pay-to-unlock results | Automatic per-request billing |
279
- | **Wallet Key** | Provided during settlement | Provided in request header |
286
+ **Step-by-step:**
280
287
 
281
- ### Middleware Configuration
288
+ 1. **Client sends request** with wallet private key in header (`x-wallet-private-key`)
289
+ 2. **Middleware forwards** request to your endpoint
290
+ 3. **Endpoint executes** and returns response with usage data
291
+ 4. **Middleware extracts** token counts (supports OpenAI, Anthropic, Google, etc.)
292
+ 5. **Settlement service calculates** cost: `(input_tokens × input_rate + output_tokens × output_rate) / 1M`
293
+ 6. **Settlement service splits** payment: 95% to recipient, 5% to Swarms Treasury
294
+ 7. **Settlement service sends** Solana transaction
295
+ 8. **Middleware adds** settlement info to response and returns to client
282
296
 
283
- Add the middleware to your FastAPI app:
297
+ ### Quick Start
284
298
 
285
299
  ```python
286
300
  from fastapi import FastAPI
@@ -291,202 +305,97 @@ app = FastAPI()
291
305
 
292
306
  app.add_middleware(
293
307
  ATPSettlementMiddleware,
294
- allowed_endpoints=[
295
- "/v1/chat",
296
- "/v1/completions",
297
- "/v1/agent/execute",
298
- ],
299
- input_cost_per_million_usd=10.0, # $10 per million input tokens
308
+ allowed_endpoints=["/v1/chat", "/v1/completions"],
309
+ input_cost_per_million_usd=10.0, # $10 per million input tokens
300
310
  output_cost_per_million_usd=30.0, # $30 per million output tokens
301
- wallet_private_key_header="x-wallet-private-key", # Header name for wallet key
302
- payment_token=PaymentToken.SOL, # SOL or USDC
303
- recipient_pubkey="YourPublicKeyHere", # Required: endpoint host receives payment
304
- skip_preflight=False, # Skip Solana transaction preflight simulation
305
- commitment="confirmed", # Solana commitment level
306
- usage_response_key="usage", # Key in response where usage data is located
307
- include_usage_in_response=True, # Add usage/cost info to response
308
- require_wallet=True, # Require wallet key (if False, skips settlement when missing)
311
+ recipient_pubkey="YourPublicKeyHere", # Your wallet receives 95%
312
+ payment_token=PaymentToken.SOL,
309
313
  )
310
314
  ```
311
315
 
312
- ### Configuration Parameters
313
-
314
- - **`allowed_endpoints`** (required): List of endpoint paths to apply settlement to (exact matches only)
315
- - **`input_cost_per_million_usd`** (required): Cost per million input tokens in USD
316
- - **`output_cost_per_million_usd`** (required): Cost per million output tokens in USD
317
- - **`wallet_private_key_header`** (default: `"x-wallet-private-key"`): HTTP header name containing the wallet private key
318
- - **`payment_token`** (default: `PaymentToken.SOL`): Token to use for payment (SOL or USDC)
319
- - **`recipient_pubkey`** (required): Solana public key of the recipient wallet (endpoint host receives main payment)
320
- - **`skip_preflight`** (default: `False`): Whether to skip preflight simulation for Solana transactions
321
- - **`commitment`** (default: `"confirmed"`): Solana commitment level (`processed`|`confirmed`|`finalized`)
322
- - **`usage_response_key`** (default: `"usage"`): Key in response JSON where usage data is located
323
- - **`include_usage_in_response`** (default: `True`): Whether to add usage/cost info to the response
324
- - **`require_wallet`** (default: `True`): Whether to require wallet private key (if False, skips settlement when missing)
325
-
326
- ### Usage Example
327
-
328
- **Client Request:**
316
+ **Client request:**
329
317
  ```bash
330
318
  curl -X POST http://localhost:8000/v1/chat \
331
- -H "Content-Type: application/json" \
332
319
  -H "x-wallet-private-key: [1,2,3,...]" \
333
- -d '{"message": "Hello, world!"}'
320
+ -d '{"message": "Hello!"}'
334
321
  ```
335
322
 
336
- **Endpoint Implementation:**
323
+ **Your endpoint:**
337
324
  ```python
338
325
  @app.post("/v1/chat")
339
- async def chat_endpoint(request: dict):
340
- # Your endpoint logic here
341
- response_data = {
342
- "output": "Hello! This is a response from the chat endpoint.",
326
+ async def chat(request: dict):
327
+ return {
328
+ "output": "Response here",
343
329
  "usage": {
344
- "input_tokens": 150, # Tokens in the request
345
- "output_tokens": 50, # Tokens in the response
346
- "total_tokens": 200,
347
- },
330
+ "input_tokens": 150,
331
+ "output_tokens": 50,
332
+ }
348
333
  }
349
- return JSONResponse(content=response_data)
350
334
  ```
351
335
 
352
- **Response (with settlement info):**
336
+ **Response includes settlement:**
353
337
  ```json
354
338
  {
355
- "output": "Hello! This is a response from the chat endpoint.",
356
- "usage": {
357
- "input_tokens": 150,
358
- "output_tokens": 50,
359
- "total_tokens": 200
360
- },
361
- "atp_usage": {
362
- "input_tokens": 150,
363
- "output_tokens": 50,
364
- "total_tokens": 200
365
- },
339
+ "output": "Response here",
340
+ "usage": {"input_tokens": 150, "output_tokens": 50},
366
341
  "atp_settlement": {
367
342
  "status": "paid",
368
343
  "transaction_signature": "5j7s8K9...",
369
- "pricing": {
370
- "usd_cost": 0.003,
371
- "source": "middleware_rates",
372
- "input_tokens": 150,
373
- "output_tokens": 50,
374
- "total_tokens": 200,
375
- "input_cost_per_million_usd": 10.0,
376
- "output_cost_per_million_usd": 30.0,
377
- "input_cost_usd": 0.0015,
378
- "output_cost_usd": 0.0015
379
- },
380
344
  "payment": {
381
- "total_amount_lamports": 300000,
382
345
  "total_amount_sol": 0.0003,
383
- "total_amount_usd": 0.003,
384
- "treasury": {
385
- "pubkey": "7MaX4muAn8ZQREJxnupm8sgokwFHujgrGfH9Qn81BuEV",
386
- "amount_lamports": 15000,
387
- "amount_sol": 0.000015,
388
- "amount_usd": 0.00015
389
- },
390
- "recipient": {
391
- "pubkey": "YourPublicKeyHere",
392
- "amount_lamports": 285000,
393
- "amount_sol": 0.000285,
394
- "amount_usd": 0.00285
395
- }
346
+ "recipient": {"amount_sol": 0.000285},
347
+ "treasury": {"amount_sol": 0.000015}
396
348
  }
397
349
  }
398
350
  }
399
351
  ```
400
352
 
401
- ### Supported Usage Formats
353
+ ### Configuration
402
354
 
403
- The middleware automatically detects and parses usage data from multiple API formats:
355
+ | Parameter | Required | Description |
356
+ |-----------|----------|-------------|
357
+ | `allowed_endpoints` | ✅ | List of paths to apply settlement (e.g., `["/v1/chat"]`) |
358
+ | `input_cost_per_million_usd` | ✅ | Cost per million input tokens |
359
+ | `output_cost_per_million_usd` | ✅ | Cost per million output tokens |
360
+ | `recipient_pubkey` | ✅ | Your Solana wallet (receives 95% of payment) |
361
+ | `wallet_private_key_header` | ❌ | Header name for wallet key (default: `x-wallet-private-key`) |
362
+ | `payment_token` | ❌ | `PaymentToken.SOL` or `PaymentToken.USDC` (default: SOL) |
363
+ | `require_wallet` | ❌ | Require wallet key or skip settlement (default: `True`) |
364
+ | `settlement_service_url` | ❌ | Settlement service URL (default: from `ATP_SETTLEMENT_URL` env) |
404
365
 
405
- - **OpenAI**: `prompt_tokens`, `completion_tokens`, `total_tokens`
406
- - **Anthropic**: `input_tokens`, `output_tokens`, `total_tokens`
407
- - **Google/Gemini**: `promptTokenCount`, `candidatesTokenCount`, `totalTokenCount`
408
- - **Cohere**: `tokens` (total), or `input_tokens`/`output_tokens` separately
409
- - **Generic**: `input_tokens`, `output_tokens`, `total_tokens`
410
- - **Nested**: `usage.prompt_tokens`, `meta.usage`, `statistics`, etc.
366
+ ### Payment Calculation
411
367
 
412
- The middleware searches for usage data in this order:
413
- 1. The configured `usage_response_key` (default: `"usage"`)
414
- 2. Top-level keys in the response
415
- 3. Nested structures (`usage`, `meta.usage`, `statistics`, etc.)
368
+ The middleware uses your configured rates to calculate cost:
416
369
 
417
- ### Payment Flow
418
-
419
- ```mermaid
420
- sequenceDiagram
421
- autonumber
422
- participant C as Client
423
- participant M as Middleware
424
- participant E as Endpoint
425
- participant S as Solana
426
-
427
- C->>M: POST /v1/chat<br/>(x-wallet-private-key header)
428
- M->>E: Forward request
429
- E-->>M: Response + usage data
430
- M->>M: Extract usage tokens<br/>(normalize format)
431
- M->>M: Calculate USD cost<br/>(from token counts)
432
- M->>M: Fetch token price<br/>(SOL/USDC)
433
- M->>M: Calculate payment amounts<br/>(with 5% fee split)
434
- M->>S: Send split payment tx<br/>(recipient + treasury)
435
- S-->>M: Transaction signature
436
- M->>M: Add settlement info<br/>to response
437
- M-->>C: Response + atp_settlement
370
+ ```
371
+ usd_cost = (input_tokens / 1,000,000 × input_rate) + (output_tokens / 1,000,000 × output_rate)
372
+ token_amount = usd_cost / token_price_usd
438
373
  ```
439
374
 
440
- ### Payment Split Logic
441
-
442
- The middleware automatically splits payments:
443
-
444
- - **Total Payment**: `usd_cost / token_price_usd`
445
- - **Swarms Treasury Fee**: `5%` of total (from `config.SETTLEMENT_FEE_PERCENT`)
446
- - **Recipient Amount**: `95%` of total (endpoint host receives this)
447
-
448
- The fee is **taken from the total** (not added on top), so the recipient receives the net amount after fees.
449
-
450
- ### Error Handling
451
-
452
- - **Missing wallet key**: Returns `401 Unauthorized` if `require_wallet=True` (default)
453
- - **Missing usage data**: Logs warning and returns original response (no settlement)
454
- - **Payment failure**: Returns `500 Internal Server Error` with error details
455
- - **Invalid private key**: Returns `500 Internal Server Error` with parsing error
456
-
457
- ### Custom API Key Integration
375
+ Payment is split automatically:
376
+ - **95%** → `recipient_pubkey` (your wallet)
377
+ - **5%** Swarms Treasury (processing fee)
458
378
 
459
- You can layer your own API key handling on top of the middleware:
379
+ The fee is **deducted from the total** (not added on top).
460
380
 
461
- ```python
462
- # Simple in-memory mapping (replace with database in production)
463
- API_KEY_TO_WALLET = {
464
- "user_api_key_123": "[1,2,3,...]", # Solana private key
465
- }
381
+ ### Supported Usage Formats
466
382
 
467
- def get_wallet_from_api_key(api_key: str = Header(..., alias="x-api-key")) -> str:
468
- """Custom dependency to map API keys to wallet private keys."""
469
- if api_key not in API_KEY_TO_WALLET:
470
- raise HTTPException(status_code=401, detail="Invalid API key")
471
- return API_KEY_TO_WALLET[api_key]
383
+ The middleware auto-detects usage from common API formats:
472
384
 
473
- # Use a custom middleware to inject wallet key from API key
474
- # before the ATP middleware processes the request
475
- ```
476
-
477
- ### When to Use Middleware vs. Main Protocol
385
+ - **OpenAI**: `prompt_tokens`, `completion_tokens`
386
+ - **Anthropic**: `input_tokens`, `output_tokens`
387
+ - **Google/Gemini**: `promptTokenCount`, `candidatesTokenCount`
388
+ - **Generic**: `input_tokens`, `output_tokens`, `total_tokens`
389
+ - **Nested**: `usage.*`, `meta.usage`, `statistics.*`
478
390
 
479
- **Use the Middleware when:**
480
- - You want automatic, per-request billing
481
- - Your API already returns usage data
482
- - You want a single-request flow (no 402 challenge)
483
- - You're building a service that charges per API call
391
+ ### Middleware vs. Main Protocol
484
392
 
485
- **Use the Main Protocol when:**
486
- - You want explicit payment approval (402 challenge)
487
- - You need to hold results until payment is confirmed
488
- - You want a two-step verification process
489
- - You're building a pay-to-unlock system
393
+ | Feature | Main Gateway | Middleware |
394
+ |---------|--------------|------------|
395
+ | **Flow** | Two-step: 402 challenge settle | Automatic: single request |
396
+ | **Use Case** | Pay-to-unlock results | Per-request billing |
397
+ | **Integration** | Two API calls | One API call |
490
398
 
491
- See `examples/middleware_usage_example.py` for a complete working example.
399
+ **Use middleware for:** Automatic billing on every request
400
+ **Use main protocol for:** Explicit payment approval before results
492
401
 
@@ -0,0 +1,9 @@
1
+ atp/__init__.py,sha256=fpKQQCi9HQEj4t2Rs5ng5qmp0KwGV6bZIl1tnS-3HQ8,251
2
+ atp/config.py,sha256=lYERbrySRTuyS_jeaAnLHYuBEQNLqaKLQTy_nEFP7lY,2041
3
+ atp/middleware.py,sha256=GlRBXEba_hlGMPi69LAGUJSQsKfmwSnAENKZQ9QcXhU,13185
4
+ atp/schemas.py,sha256=iASVBPpAhrO-LDXs2UC9ABtfV1oDYsu4kZcz3dVeJNA,6220
5
+ atp/settlement_client.py,sha256=jfvhxDqp2kDISWG7tjX5s88goAPxp-lMXvuJtpMyOys,6742
6
+ atp_protocol-1.2.0.dist-info/LICENSE,sha256=xx0jnfkXJvxRnG63LTGOxlggYnIysveWIZ6H3PNdCrQ,11357
7
+ atp_protocol-1.2.0.dist-info/METADATA,sha256=r-Q1CFshMWPS-jftoGwI7OuPcj8B609IRGcBe00VX0E,14295
8
+ atp_protocol-1.2.0.dist-info/WHEEL,sha256=b4K_helf-jlQoXBBETfwnf4B04YC67LOev0jo4fX5m8,88
9
+ atp_protocol-1.2.0.dist-info/RECORD,,