traia-iatp 0.1.2__py3-none-any.whl → 0.1.67__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.
Files changed (95) hide show
  1. traia_iatp/__init__.py +105 -8
  2. traia_iatp/cli/main.py +85 -1
  3. traia_iatp/client/__init__.py +28 -3
  4. traia_iatp/client/crewai_a2a_tools.py +32 -12
  5. traia_iatp/client/d402_a2a_client.py +348 -0
  6. traia_iatp/contracts/__init__.py +11 -0
  7. traia_iatp/contracts/data/abis/contract-abis-localhost.json +4091 -0
  8. traia_iatp/contracts/data/abis/contract-abis-sepolia.json +4890 -0
  9. traia_iatp/contracts/data/addresses/contract-addresses.json +17 -0
  10. traia_iatp/contracts/data/addresses/contract-proxies.json +12 -0
  11. traia_iatp/contracts/iatp_contracts_config.py +263 -0
  12. traia_iatp/contracts/wallet_creator.py +369 -0
  13. traia_iatp/core/models.py +17 -3
  14. traia_iatp/d402/MIDDLEWARE_ARCHITECTURE.md +205 -0
  15. traia_iatp/d402/PRICE_BUILDER_USAGE.md +249 -0
  16. traia_iatp/d402/README.md +489 -0
  17. traia_iatp/d402/__init__.py +54 -0
  18. traia_iatp/d402/asgi_wrapper.py +469 -0
  19. traia_iatp/d402/chains.py +102 -0
  20. traia_iatp/d402/client.py +150 -0
  21. traia_iatp/d402/clients/__init__.py +7 -0
  22. traia_iatp/d402/clients/base.py +218 -0
  23. traia_iatp/d402/clients/httpx.py +266 -0
  24. traia_iatp/d402/common.py +114 -0
  25. traia_iatp/d402/encoding.py +28 -0
  26. traia_iatp/d402/examples/client_example.py +197 -0
  27. traia_iatp/d402/examples/server_example.py +171 -0
  28. traia_iatp/d402/facilitator.py +481 -0
  29. traia_iatp/d402/mcp_middleware.py +296 -0
  30. traia_iatp/d402/models.py +116 -0
  31. traia_iatp/d402/networks.py +98 -0
  32. traia_iatp/d402/path.py +43 -0
  33. traia_iatp/d402/payment_introspection.py +126 -0
  34. traia_iatp/d402/payment_signing.py +183 -0
  35. traia_iatp/d402/price_builder.py +164 -0
  36. traia_iatp/d402/servers/__init__.py +61 -0
  37. traia_iatp/d402/servers/base.py +139 -0
  38. traia_iatp/d402/servers/example_general_server.py +140 -0
  39. traia_iatp/d402/servers/fastapi.py +253 -0
  40. traia_iatp/d402/servers/mcp.py +304 -0
  41. traia_iatp/d402/servers/starlette.py +878 -0
  42. traia_iatp/d402/starlette_middleware.py +529 -0
  43. traia_iatp/d402/types.py +300 -0
  44. traia_iatp/mcp/D402_MCP_ADAPTER_FLOW.md +357 -0
  45. traia_iatp/mcp/__init__.py +3 -0
  46. traia_iatp/mcp/d402_mcp_tool_adapter.py +526 -0
  47. traia_iatp/mcp/mcp_agent_template.py +78 -13
  48. traia_iatp/mcp/templates/Dockerfile.j2 +27 -4
  49. traia_iatp/mcp/templates/README.md.j2 +104 -8
  50. traia_iatp/mcp/templates/cursor-rules.md.j2 +194 -0
  51. traia_iatp/mcp/templates/deployment_params.json.j2 +1 -2
  52. traia_iatp/mcp/templates/docker-compose.yml.j2 +13 -3
  53. traia_iatp/mcp/templates/env.example.j2 +60 -0
  54. traia_iatp/mcp/templates/mcp_health_check.py.j2 +2 -2
  55. traia_iatp/mcp/templates/pyproject.toml.j2 +11 -5
  56. traia_iatp/mcp/templates/pyrightconfig.json.j2 +22 -0
  57. traia_iatp/mcp/templates/run_local_docker.sh.j2 +320 -10
  58. traia_iatp/mcp/templates/server.py.j2 +174 -197
  59. traia_iatp/mcp/traia_mcp_adapter.py +182 -20
  60. traia_iatp/registry/__init__.py +47 -12
  61. traia_iatp/registry/atlas_search_indexes.json +108 -54
  62. traia_iatp/registry/iatp_search_api.py +169 -39
  63. traia_iatp/registry/mongodb_registry.py +241 -69
  64. traia_iatp/registry/readmes/EMBEDDINGS_SETUP.md +1 -1
  65. traia_iatp/registry/readmes/IATP_SEARCH_API_GUIDE.md +8 -8
  66. traia_iatp/registry/readmes/MONGODB_X509_AUTH.md +1 -1
  67. traia_iatp/registry/readmes/README.md +3 -3
  68. traia_iatp/registry/readmes/REFACTORING_SUMMARY.md +6 -6
  69. traia_iatp/scripts/__init__.py +2 -0
  70. traia_iatp/scripts/create_wallet.py +244 -0
  71. traia_iatp/server/a2a_server.py +22 -7
  72. traia_iatp/server/iatp_server_template_generator.py +23 -0
  73. traia_iatp/server/templates/.dockerignore.j2 +48 -0
  74. traia_iatp/server/templates/Dockerfile.j2 +23 -1
  75. traia_iatp/server/templates/README.md +2 -2
  76. traia_iatp/server/templates/README.md.j2 +5 -5
  77. traia_iatp/server/templates/__main__.py.j2 +374 -66
  78. traia_iatp/server/templates/agent.py.j2 +12 -11
  79. traia_iatp/server/templates/agent_config.json.j2 +3 -3
  80. traia_iatp/server/templates/agent_executor.py.j2 +45 -27
  81. traia_iatp/server/templates/env.example.j2 +32 -4
  82. traia_iatp/server/templates/gitignore.j2 +7 -0
  83. traia_iatp/server/templates/pyproject.toml.j2 +13 -12
  84. traia_iatp/server/templates/run_local_docker.sh.j2 +143 -11
  85. traia_iatp/server/templates/server.py.j2 +197 -10
  86. traia_iatp/special_agencies/registry_search_agency.py +1 -1
  87. traia_iatp/utils/iatp_utils.py +6 -6
  88. traia_iatp-0.1.67.dist-info/METADATA +320 -0
  89. traia_iatp-0.1.67.dist-info/RECORD +117 -0
  90. traia_iatp-0.1.2.dist-info/METADATA +0 -414
  91. traia_iatp-0.1.2.dist-info/RECORD +0 -72
  92. {traia_iatp-0.1.2.dist-info → traia_iatp-0.1.67.dist-info}/WHEEL +0 -0
  93. {traia_iatp-0.1.2.dist-info → traia_iatp-0.1.67.dist-info}/entry_points.txt +0 -0
  94. {traia_iatp-0.1.2.dist-info → traia_iatp-0.1.67.dist-info}/licenses/LICENSE +0 -0
  95. {traia_iatp-0.1.2.dist-info → traia_iatp-0.1.67.dist-info}/top_level.txt +0 -0
@@ -0,0 +1,114 @@
1
+ from decimal import Decimal
2
+ from typing import List, Optional
3
+
4
+ from .chains import (
5
+ get_chain_id,
6
+ get_token_decimals,
7
+ get_token_name,
8
+ get_token_version,
9
+ get_default_token_address,
10
+ )
11
+ from .types import Price, TokenAmount, PaymentRequirements, PaymentPayload
12
+
13
+
14
+ def parse_money(amount: str | int, address: str, network: str) -> int:
15
+ """Parse money string or int into int
16
+
17
+ Params:
18
+ amount: str | int - if int, should be the full amount including token specific decimals
19
+ """
20
+ if isinstance(amount, str):
21
+ if amount.startswith("$"):
22
+ amount = amount[1:]
23
+ decimal_amount = Decimal(amount)
24
+
25
+ chain_id = get_chain_id(network)
26
+ decimals = get_token_decimals(chain_id, address)
27
+ decimal_amount = decimal_amount * Decimal(10**decimals)
28
+ return int(decimal_amount)
29
+ return amount
30
+
31
+
32
+ def process_price_to_atomic_amount(
33
+ price: Price, network: str
34
+ ) -> tuple[str, str, dict[str, str]]:
35
+ """Process a Price into atomic amount, asset address, and EIP-712 domain info
36
+
37
+ Args:
38
+ price: Either Money (USD string/int) or TokenAmount
39
+ network: Network identifier
40
+
41
+ Returns:
42
+ Tuple of (max_amount_required, asset_address, eip712_domain)
43
+
44
+ Raises:
45
+ ValueError: If price format is invalid
46
+ """
47
+ if isinstance(price, (str, int)):
48
+ # Money type - convert USD to USDC atomic units
49
+ try:
50
+ if isinstance(price, str) and price.startswith("$"):
51
+ price = price[1:]
52
+ amount = Decimal(str(price))
53
+
54
+ # Get USDC address for the network
55
+ chain_id = get_chain_id(network)
56
+ asset_address = get_usdc_address(chain_id)
57
+ decimals = get_token_decimals(chain_id, asset_address)
58
+
59
+ # Convert to atomic units
60
+ atomic_amount = int(amount * Decimal(10**decimals))
61
+
62
+ # Get EIP-712 domain info
63
+ eip712_domain = {
64
+ "name": get_token_name(chain_id, asset_address),
65
+ "version": get_token_version(chain_id, asset_address),
66
+ }
67
+
68
+ return str(atomic_amount), asset_address, eip712_domain
69
+
70
+ except (ValueError, KeyError) as e:
71
+ raise ValueError(f"Invalid price format: {price}. Error: {e}")
72
+
73
+ elif isinstance(price, TokenAmount):
74
+ # TokenAmount type - already in atomic units with asset info
75
+ return (
76
+ price.amount,
77
+ price.asset.address,
78
+ {
79
+ "name": price.asset.eip712.name,
80
+ "version": price.asset.eip712.version,
81
+ },
82
+ )
83
+
84
+ else:
85
+ raise ValueError(f"Invalid price type: {type(price)}")
86
+
87
+
88
+ def get_usdc_address(chain_id: int | str) -> str:
89
+ """Get the USDC contract address for a given chain ID"""
90
+ chain_id_str = str(chain_id) # Convert to string for consistency
91
+ return get_default_token_address(chain_id_str, "usdc")
92
+
93
+
94
+ def find_matching_payment_requirements(
95
+ payment_requirements: List[PaymentRequirements],
96
+ payment: PaymentPayload,
97
+ ) -> Optional[PaymentRequirements]:
98
+ """
99
+ Finds the matching payment requirements for the given payment.
100
+
101
+ Args:
102
+ payment_requirements: The payment requirements to search through
103
+ payment: The payment to match against
104
+
105
+ Returns:
106
+ The matching payment requirements or None if no match is found
107
+ """
108
+ for req in payment_requirements:
109
+ if req.scheme == payment.scheme and req.network == payment.network:
110
+ return req
111
+ return None
112
+
113
+
114
+ d402_VERSION = 1
@@ -0,0 +1,28 @@
1
+ import base64
2
+ from typing import Union
3
+
4
+
5
+ def safe_base64_encode(data: Union[str, bytes]) -> str:
6
+ """Safely encode string or bytes to base64 string.
7
+
8
+ Args:
9
+ data: String or bytes to encode
10
+
11
+ Returns:
12
+ Base64 encoded string
13
+ """
14
+ if isinstance(data, str):
15
+ data = data.encode("utf-8")
16
+ return base64.b64encode(data).decode("utf-8")
17
+
18
+
19
+ def safe_base64_decode(data: str) -> str:
20
+ """Safely decode base64 string to bytes and then to utf-8 string.
21
+
22
+ Args:
23
+ data: Base64 encoded string
24
+
25
+ Returns:
26
+ Decoded utf-8 string
27
+ """
28
+ return base64.b64decode(data).decode("utf-8")
@@ -0,0 +1,197 @@
1
+ """Example: Client agent making payments to utility agents."""
2
+
3
+ import os
4
+ import asyncio
5
+ from traia_iatp.client import create_d402_a2a_client
6
+
7
+
8
+ async def example_simple_payment():
9
+ """Example 1: Simple payment to a utility agent."""
10
+ print("=" * 60)
11
+ print("Example 1: Simple Payment")
12
+ print("=" * 60)
13
+
14
+ # Create client with payment support
15
+ client = create_d402_a2a_client(
16
+ agent_endpoint="https://sentiment-agent.traia.io",
17
+ payment_private_key=os.getenv("CLIENT_PRIVATE_KEY"),
18
+ max_payment_usd=5.0 # Maximum $5 per request
19
+ )
20
+
21
+ # Send message - automatically handles payment if required
22
+ try:
23
+ response = await client.send_message_with_payment(
24
+ "Analyze sentiment: 'Tech stocks rally on strong earnings'"
25
+ )
26
+ print(f"Response: {response}")
27
+ except ValueError as e:
28
+ print(f"Payment error: {e}")
29
+ except Exception as e:
30
+ print(f"Error: {e}")
31
+
32
+
33
+ async def example_crewai_integration():
34
+ """Example 2: Using paid agents in CrewAI."""
35
+ print("\n" + "=" * 60)
36
+ print("Example 2: CrewAI Integration with Paid Agents")
37
+ print("=" * 60)
38
+
39
+ from crewai import Agent, Task, Crew
40
+ from traia_iatp.client.crewai_a2a_tools import A2AToolkit
41
+ from traia_iatp.registry.iatp_search_api import find_utility_agent
42
+
43
+ # Find the utility agent
44
+ agent_info = find_utility_agent(agent_id="finbert-sentiment-agent")
45
+
46
+ if not agent_info:
47
+ print("Agent not found in registry")
48
+ return
49
+
50
+ print(f"Found agent: {agent_info.name}")
51
+ print(f"D402 Enabled: {agent_info.d402_enabled}")
52
+
53
+ # Create tool with payment support
54
+ sentiment_tool = A2AToolkit.create_tool_from_endpoint(
55
+ endpoint=agent_info.base_url,
56
+ name=agent_info.name,
57
+ description=agent_info.description,
58
+ # Payment configuration
59
+ payment_private_key=os.getenv("CLIENT_PRIVATE_KEY"),
60
+ max_payment_usd=1.0
61
+ )
62
+
63
+ # Create CrewAI agent
64
+ analyst = Agent(
65
+ role="Financial Sentiment Analyst",
66
+ goal="Analyze sentiment of financial news",
67
+ backstory="Expert financial analyst with access to AI sentiment tools",
68
+ tools=[sentiment_tool],
69
+ verbose=True
70
+ )
71
+
72
+ # Create task
73
+ task = Task(
74
+ description="Analyze sentiment of: 'Federal Reserve signals rate cuts ahead'",
75
+ expected_output="Sentiment analysis with confidence scores",
76
+ agent=analyst
77
+ )
78
+
79
+ # Run crew
80
+ crew = Crew(agents=[analyst], tasks=[task])
81
+ result = crew.kickoff()
82
+
83
+ print(f"\nResult: {result}")
84
+
85
+
86
+ async def example_multiple_agents():
87
+ """Example 3: Using multiple paid utility agents."""
88
+ print("\n" + "=" * 60)
89
+ print("Example 3: Multiple Paid Agents")
90
+ print("=" * 60)
91
+
92
+ from traia_iatp.registry.iatp_search_api import search_utility_agents
93
+ from traia_iatp.client import create_d402_a2a_client
94
+
95
+ # Search for sentiment analysis agents
96
+ agents = search_utility_agents(
97
+ query="sentiment analysis",
98
+ limit=5
99
+ )
100
+
101
+ print(f"Found {len(agents)} sentiment analysis agents")
102
+
103
+ # Filter for paid agents
104
+ paid_agents = [a for a in agents if a.d402_enabled]
105
+ print(f"Found {len(paid_agents)} paid agents")
106
+
107
+ # Create clients for each paid agent
108
+ for agent_info in paid_agents[:2]: # Try first 2
109
+ print(f"\n--- Testing {agent_info.name} ---")
110
+
111
+ client = create_d402_a2a_client(
112
+ agent_endpoint=agent_info.base_url,
113
+ payment_private_key=os.getenv("CLIENT_PRIVATE_KEY"),
114
+ max_payment_usd=1.0
115
+ )
116
+
117
+ try:
118
+ response = await client.send_message_with_payment(
119
+ "Analyze: 'Stock market reaches all-time high'"
120
+ )
121
+ print(f"Response: {response}")
122
+ except Exception as e:
123
+ print(f"Error: {e}")
124
+
125
+
126
+ async def example_payment_limits():
127
+ """Example 4: Payment limits and error handling."""
128
+ print("\n" + "=" * 60)
129
+ print("Example 4: Payment Limits and Error Handling")
130
+ print("=" * 60)
131
+
132
+ # Create client with very low payment limit
133
+ client = create_d402_a2a_client(
134
+ agent_endpoint="https://expensive-agent.traia.io",
135
+ payment_private_key=os.getenv("CLIENT_PRIVATE_KEY"),
136
+ max_payment_usd=0.001 # Only allow $0.001
137
+ )
138
+
139
+ try:
140
+ response = await client.send_message_with_payment("test")
141
+ print(f"Response: {response}")
142
+ except ValueError as e:
143
+ print(f"Payment rejected: {e}")
144
+ print("Solution: Increase max_payment_usd or choose a cheaper agent")
145
+
146
+
147
+ async def example_check_agent_pricing():
148
+ """Example 5: Check agent pricing before calling."""
149
+ print("\n" + "=" * 60)
150
+ print("Example 5: Check Pricing Before Calling")
151
+ print("=" * 60)
152
+
153
+ from traia_iatp.registry.iatp_search_api import find_utility_agent
154
+
155
+ # Find agent and check pricing
156
+ agent_info = find_utility_agent(agent_id="finbert-sentiment-agent")
157
+
158
+ if agent_info and agent_info.d402_enabled:
159
+ payment_info = agent_info.d402_payment_info
160
+
161
+ print(f"Agent: {agent_info.name}")
162
+ print(f"D402 Enabled: {agent_info.d402_enabled}")
163
+
164
+ if payment_info:
165
+ default_price = payment_info.get("defaultPrice", {})
166
+ print(f"Default Price: ${default_price.get('usdAmount', 'N/A')} USD")
167
+ print(f"Network: {default_price.get('network', 'N/A')}")
168
+ print(f"Asset: {default_price.get('asset', 'N/A')}")
169
+
170
+ skill_prices = payment_info.get("skillPrices", {})
171
+ if skill_prices:
172
+ print("\nSkill-specific pricing:")
173
+ for skill_id, price in skill_prices.items():
174
+ print(f" {skill_id}: ${price.get('usdAmount', 'N/A')} USD")
175
+
176
+
177
+ async def main():
178
+ """Run all examples."""
179
+ # Example 1: Simple payment
180
+ await example_simple_payment()
181
+
182
+ # Example 2: CrewAI integration
183
+ await example_crewai_integration()
184
+
185
+ # Example 3: Multiple agents
186
+ await example_multiple_agents()
187
+
188
+ # Example 4: Payment limits
189
+ await example_payment_limits()
190
+
191
+ # Example 5: Check pricing
192
+ await example_check_agent_pricing()
193
+
194
+
195
+ if __name__ == "__main__":
196
+ asyncio.run(main())
197
+
@@ -0,0 +1,171 @@
1
+ """Example: Utility agent with d402 payment support."""
2
+
3
+ import os
4
+ import asyncio
5
+ from fastapi import FastAPI, Request
6
+ import uvicorn
7
+
8
+ from traia_iatp.d402 import (
9
+ D402Config,
10
+ D402ServicePrice,
11
+ require_iatp_payment,
12
+ add_d402_info_to_agent_card
13
+ )
14
+
15
+ # Initialize FastAPI app
16
+ app = FastAPI(title="Paid Sentiment Analysis Agent")
17
+
18
+ # Configure d402 payments
19
+ d402_config = D402Config(
20
+ enabled=True,
21
+ # This would be the deployed utility agent contract address
22
+ pay_to_address=os.getenv("UTILITY_AGENT_CONTRACT_ADDRESS", "0x1234567890123456789012345678901234567890"),
23
+
24
+ # Default pricing: $0.01 per request
25
+ default_price=D402ServicePrice(
26
+ usd_amount="0.01",
27
+ network="base-mainnet",
28
+ asset_address="0x833589fCD6eDb6E08f4c7C32D4f71b54bdA02913", # USDC on Base
29
+ max_timeout_seconds=300
30
+ ),
31
+
32
+ # Custom pricing per skill
33
+ skill_prices={
34
+ "sentiment_analysis": D402ServicePrice(
35
+ usd_amount="0.01",
36
+ network="base-mainnet",
37
+ asset_address="0x833589fCD6eDb6E08f4c7C32D4f71b54bdA02913",
38
+ max_timeout_seconds=300
39
+ ),
40
+ "entity_extraction": D402ServicePrice(
41
+ usd_amount="0.02",
42
+ network="base-mainnet",
43
+ asset_address="0x833589fCD6eDb6E08f4c7C32D4f71b54bdA02913",
44
+ max_timeout_seconds=300
45
+ )
46
+ },
47
+
48
+ # Facilitator configuration
49
+ facilitator_url=os.getenv("FACILITATOR_URL", "https://api.traia.io/d402/facilitator"),
50
+ facilitator_api_key=os.getenv("TRAIA_RELAYER_API_KEY"),
51
+
52
+ # Service description for payment UI
53
+ service_description="AI-powered financial sentiment analysis using FinBERT models",
54
+
55
+ # Protect all paths by default
56
+ protected_paths=["*"]
57
+ )
58
+
59
+
60
+ # Add d402 middleware
61
+ @app.middleware("http")
62
+ async def payment_middleware(request: Request, call_next):
63
+ """Middleware that requires payment for all requests."""
64
+ middleware = require_iatp_payment(d402_config)
65
+ return await middleware(request, call_next)
66
+
67
+
68
+ # Agent card endpoint (standard A2A protocol)
69
+ @app.get("/.well-known/agent.json")
70
+ async def agent_card():
71
+ """Return agent card with d402 payment information."""
72
+ card = {
73
+ "name": "sentiment_analysis_agent",
74
+ "description": "AI-powered financial sentiment analysis",
75
+ "version": "1.0.0",
76
+ "capabilities": {
77
+ "streaming": False,
78
+ "pushNotifications": False,
79
+ "stateTransitionHistory": False
80
+ },
81
+ "skills": [
82
+ {
83
+ "id": "sentiment_analysis",
84
+ "name": "Sentiment Analysis",
85
+ "description": "Analyze sentiment of financial text",
86
+ "examples": [
87
+ "Analyze: 'Tech stocks rally on strong earnings'",
88
+ "What is the sentiment of: 'Markets tumble amid recession fears'"
89
+ ]
90
+ }
91
+ ]
92
+ }
93
+
94
+ # Add d402 payment information
95
+ card = await add_d402_info_to_agent_card(card, d402_config)
96
+ return card
97
+
98
+
99
+ # Protected endpoint - requires payment
100
+ @app.post("/analyze")
101
+ async def analyze_sentiment(request: Request):
102
+ """Analyze sentiment of text. Requires payment."""
103
+ data = await request.json()
104
+ text = data.get("text", "")
105
+
106
+ # Simulate sentiment analysis
107
+ # In real implementation, this would call a model
108
+ result = {
109
+ "text": text,
110
+ "sentiment": "positive",
111
+ "confidence": 0.87,
112
+ "scores": {
113
+ "positive": 0.87,
114
+ "neutral": 0.08,
115
+ "negative": 0.05
116
+ }
117
+ }
118
+
119
+ return result
120
+
121
+
122
+ # Another protected endpoint
123
+ @app.post("/extract-entities")
124
+ async def extract_entities(request: Request):
125
+ """Extract entities from text. Requires payment (higher price)."""
126
+ data = await request.json()
127
+ text = data.get("text", "")
128
+
129
+ # Simulate entity extraction
130
+ result = {
131
+ "text": text,
132
+ "entities": [
133
+ {"text": "Apple", "type": "ORGANIZATION", "confidence": 0.95},
134
+ {"text": "Tim Cook", "type": "PERSON", "confidence": 0.92}
135
+ ]
136
+ }
137
+
138
+ return result
139
+
140
+
141
+ # Health check endpoint (not protected)
142
+ @app.get("/health")
143
+ async def health():
144
+ """Health check endpoint - no payment required."""
145
+ # This endpoint bypasses payment because we can add specific path exclusions
146
+ return {"status": "healthy"}
147
+
148
+
149
+ def main():
150
+ """Run the server."""
151
+ print("Starting Paid Sentiment Analysis Agent")
152
+ print(f"D402 Enabled: {d402_config.enabled}")
153
+ print(f"Pay-to Address: {d402_config.pay_to_address}")
154
+ print(f"Default Price: ${d402_config.default_price.usd_amount} USD")
155
+ print(f"Facilitator: {d402_config.facilitator_url}")
156
+ print()
157
+ print("Protected endpoints:")
158
+ print(" POST /analyze - $0.01 per request")
159
+ print(" POST /extract-entities - $0.02 per request")
160
+ print()
161
+ print("Free endpoints:")
162
+ print(" GET /health")
163
+ print(" GET /.well-known/agent.json")
164
+ print()
165
+
166
+ uvicorn.run(app, host="0.0.0.0", port=8000)
167
+
168
+
169
+ if __name__ == "__main__":
170
+ main()
171
+