traia-iatp 0.1.29__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.
Potentially problematic release.
This version of traia-iatp might be problematic. Click here for more details.
- traia_iatp/README.md +368 -0
- traia_iatp/__init__.py +54 -0
- traia_iatp/cli/__init__.py +5 -0
- traia_iatp/cli/main.py +483 -0
- traia_iatp/client/__init__.py +10 -0
- traia_iatp/client/a2a_client.py +274 -0
- traia_iatp/client/crewai_a2a_tools.py +335 -0
- traia_iatp/client/d402_a2a_client.py +293 -0
- traia_iatp/client/grpc_a2a_tools.py +349 -0
- traia_iatp/client/root_path_a2a_client.py +1 -0
- traia_iatp/contracts/__init__.py +12 -0
- traia_iatp/contracts/iatp_contracts_config.py +263 -0
- traia_iatp/contracts/wallet_creator.py +255 -0
- traia_iatp/core/__init__.py +43 -0
- traia_iatp/core/models.py +172 -0
- traia_iatp/d402/__init__.py +55 -0
- traia_iatp/d402/chains.py +102 -0
- traia_iatp/d402/client.py +150 -0
- traia_iatp/d402/clients/__init__.py +7 -0
- traia_iatp/d402/clients/base.py +218 -0
- traia_iatp/d402/clients/httpx.py +219 -0
- traia_iatp/d402/common.py +114 -0
- traia_iatp/d402/encoding.py +28 -0
- traia_iatp/d402/examples/client_example.py +197 -0
- traia_iatp/d402/examples/server_example.py +171 -0
- traia_iatp/d402/facilitator.py +453 -0
- traia_iatp/d402/fastapi_middleware/__init__.py +6 -0
- traia_iatp/d402/fastapi_middleware/middleware.py +225 -0
- traia_iatp/d402/fastmcp_middleware.py +147 -0
- traia_iatp/d402/mcp_middleware.py +434 -0
- traia_iatp/d402/middleware.py +193 -0
- traia_iatp/d402/models.py +116 -0
- traia_iatp/d402/networks.py +98 -0
- traia_iatp/d402/path.py +43 -0
- traia_iatp/d402/payment_introspection.py +104 -0
- traia_iatp/d402/payment_signing.py +178 -0
- traia_iatp/d402/paywall.py +119 -0
- traia_iatp/d402/starlette_middleware.py +326 -0
- traia_iatp/d402/template.py +1 -0
- traia_iatp/d402/types.py +300 -0
- traia_iatp/mcp/__init__.py +18 -0
- traia_iatp/mcp/client.py +201 -0
- traia_iatp/mcp/d402_mcp_tool_adapter.py +361 -0
- traia_iatp/mcp/mcp_agent_template.py +481 -0
- traia_iatp/mcp/templates/Dockerfile.j2 +80 -0
- traia_iatp/mcp/templates/README.md.j2 +310 -0
- traia_iatp/mcp/templates/cursor-rules.md.j2 +520 -0
- traia_iatp/mcp/templates/deployment_params.json.j2 +20 -0
- traia_iatp/mcp/templates/docker-compose.yml.j2 +32 -0
- traia_iatp/mcp/templates/dockerignore.j2 +47 -0
- traia_iatp/mcp/templates/env.example.j2 +57 -0
- traia_iatp/mcp/templates/gitignore.j2 +77 -0
- traia_iatp/mcp/templates/mcp_health_check.py.j2 +150 -0
- traia_iatp/mcp/templates/pyproject.toml.j2 +32 -0
- traia_iatp/mcp/templates/pyrightconfig.json.j2 +22 -0
- traia_iatp/mcp/templates/run_local_docker.sh.j2 +390 -0
- traia_iatp/mcp/templates/server.py.j2 +175 -0
- traia_iatp/mcp/traia_mcp_adapter.py +543 -0
- traia_iatp/preview_diagrams.html +181 -0
- traia_iatp/registry/__init__.py +26 -0
- traia_iatp/registry/atlas_search_indexes.json +280 -0
- traia_iatp/registry/embeddings.py +298 -0
- traia_iatp/registry/iatp_search_api.py +846 -0
- traia_iatp/registry/mongodb_registry.py +771 -0
- traia_iatp/registry/readmes/ATLAS_SEARCH_INDEXES.md +252 -0
- traia_iatp/registry/readmes/ATLAS_SEARCH_SETUP.md +134 -0
- traia_iatp/registry/readmes/AUTHENTICATION_UPDATE.md +124 -0
- traia_iatp/registry/readmes/EMBEDDINGS_SETUP.md +172 -0
- traia_iatp/registry/readmes/IATP_SEARCH_API_GUIDE.md +257 -0
- traia_iatp/registry/readmes/MONGODB_X509_AUTH.md +208 -0
- traia_iatp/registry/readmes/README.md +251 -0
- traia_iatp/registry/readmes/REFACTORING_SUMMARY.md +191 -0
- traia_iatp/scripts/__init__.py +2 -0
- traia_iatp/scripts/create_wallet.py +244 -0
- traia_iatp/server/__init__.py +15 -0
- traia_iatp/server/a2a_server.py +219 -0
- traia_iatp/server/example_template_usage.py +72 -0
- traia_iatp/server/iatp_server_agent_generator.py +237 -0
- traia_iatp/server/iatp_server_template_generator.py +235 -0
- traia_iatp/server/templates/.dockerignore.j2 +48 -0
- traia_iatp/server/templates/Dockerfile.j2 +49 -0
- traia_iatp/server/templates/README.md +137 -0
- traia_iatp/server/templates/README.md.j2 +425 -0
- traia_iatp/server/templates/__init__.py +1 -0
- traia_iatp/server/templates/__main__.py.j2 +565 -0
- traia_iatp/server/templates/agent.py.j2 +94 -0
- traia_iatp/server/templates/agent_config.json.j2 +22 -0
- traia_iatp/server/templates/agent_executor.py.j2 +279 -0
- traia_iatp/server/templates/docker-compose.yml.j2 +23 -0
- traia_iatp/server/templates/env.example.j2 +84 -0
- traia_iatp/server/templates/gitignore.j2 +78 -0
- traia_iatp/server/templates/grpc_server.py.j2 +218 -0
- traia_iatp/server/templates/pyproject.toml.j2 +78 -0
- traia_iatp/server/templates/run_local_docker.sh.j2 +103 -0
- traia_iatp/server/templates/server.py.j2 +243 -0
- traia_iatp/special_agencies/__init__.py +4 -0
- traia_iatp/special_agencies/registry_search_agency.py +392 -0
- traia_iatp/utils/__init__.py +10 -0
- traia_iatp/utils/docker_utils.py +251 -0
- traia_iatp/utils/general.py +64 -0
- traia_iatp/utils/iatp_utils.py +126 -0
- traia_iatp-0.1.29.dist-info/METADATA +423 -0
- traia_iatp-0.1.29.dist-info/RECORD +107 -0
- traia_iatp-0.1.29.dist-info/WHEEL +5 -0
- traia_iatp-0.1.29.dist-info/entry_points.txt +2 -0
- traia_iatp-0.1.29.dist-info/licenses/LICENSE +21 -0
- traia_iatp-0.1.29.dist-info/top_level.txt +1 -0
|
@@ -0,0 +1,172 @@
|
|
|
1
|
+
"""Core data models for IATP."""
|
|
2
|
+
|
|
3
|
+
from datetime import datetime
|
|
4
|
+
from typing import Optional, Dict, Any, List
|
|
5
|
+
from pydantic import BaseModel, Field, HttpUrl
|
|
6
|
+
from enum import Enum
|
|
7
|
+
|
|
8
|
+
|
|
9
|
+
class MCPServerType(str, Enum):
|
|
10
|
+
"""Types of MCP servers."""
|
|
11
|
+
STREAMABLE_HTTP = "streamable-http"
|
|
12
|
+
|
|
13
|
+
|
|
14
|
+
class MCPServer(BaseModel):
|
|
15
|
+
"""MCP Server specification."""
|
|
16
|
+
id: Optional[str] = Field(default=None, description="Unique identifier")
|
|
17
|
+
name: str = Field(..., description="Name of the MCP server")
|
|
18
|
+
url: str = Field(..., description="URL or path to connect to the MCP server")
|
|
19
|
+
server_type: MCPServerType = Field(default=MCPServerType.STREAMABLE_HTTP, description="Type of MCP server connection")
|
|
20
|
+
description: str = Field(..., description="Description of what the server enables")
|
|
21
|
+
capabilities: List[str] = Field(default_factory=list, description="List of capabilities/APIs")
|
|
22
|
+
metadata: Dict[str, Any] = Field(default_factory=dict, description="Additional metadata")
|
|
23
|
+
|
|
24
|
+
class ConfigDict:
|
|
25
|
+
json_encoders = {HttpUrl: str}
|
|
26
|
+
|
|
27
|
+
|
|
28
|
+
class AgentSkill(BaseModel):
|
|
29
|
+
"""IATP Agent skill definition."""
|
|
30
|
+
id: str = Field(..., description="Unique skill identifier")
|
|
31
|
+
name: str = Field(..., description="Human-readable skill name")
|
|
32
|
+
description: str = Field(..., description="Detailed skill description")
|
|
33
|
+
examples: List[str] = Field(default_factory=list, description="Example usage patterns")
|
|
34
|
+
input_modes: List[str] = Field(default_factory=list, description="Supported input modes")
|
|
35
|
+
output_modes: List[str] = Field(default_factory=list, description="Supported output modes")
|
|
36
|
+
tags: List[str] = Field(default_factory=list, description="Tags for categorization")
|
|
37
|
+
|
|
38
|
+
|
|
39
|
+
class AgentCapabilities(BaseModel):
|
|
40
|
+
"""IATP Agent capabilities."""
|
|
41
|
+
streaming: bool = Field(default=False, description="Supports SSE streaming")
|
|
42
|
+
push_notifications: bool = Field(default=False, description="Supports push notifications")
|
|
43
|
+
state_transition_history: bool = Field(default=False, description="Maintains state history")
|
|
44
|
+
custom_features: Dict[str, Any] = Field(default_factory=dict, description="Custom capabilities")
|
|
45
|
+
|
|
46
|
+
|
|
47
|
+
class AgentCard(BaseModel):
|
|
48
|
+
"""IATP Agent card for discovery and initialization."""
|
|
49
|
+
name: str = Field(..., description="Agent name")
|
|
50
|
+
description: str = Field(..., description="Agent description")
|
|
51
|
+
version: str = Field(..., description="Agent version")
|
|
52
|
+
skills: List[AgentSkill] = Field(default_factory=list, description="Available skills")
|
|
53
|
+
capabilities: AgentCapabilities = Field(default_factory=AgentCapabilities, description="Agent capabilities")
|
|
54
|
+
default_input_modes: List[str] = Field(default_factory=list, description="Default input modes")
|
|
55
|
+
default_output_modes: List[str] = Field(default_factory=list, description="Default output modes")
|
|
56
|
+
metadata: Dict[str, Any] = Field(default_factory=dict, description="Additional metadata")
|
|
57
|
+
|
|
58
|
+
|
|
59
|
+
class IATPEndpoints(BaseModel):
|
|
60
|
+
"""IATP server endpoints configuration.
|
|
61
|
+
|
|
62
|
+
Note: The A2A protocol only defines a minimal set of endpoints:
|
|
63
|
+
- Root path (/) for JSON-RPC
|
|
64
|
+
- /.well-known/agent.json for agent card
|
|
65
|
+
- /a2a/tasks/* for SSE subscriptions (if streaming is supported)
|
|
66
|
+
|
|
67
|
+
Health and info endpoints are NOT part of the A2A protocol standard.
|
|
68
|
+
"""
|
|
69
|
+
base_url: str = Field(..., description="Base URL of the IATP server")
|
|
70
|
+
iatp_endpoint: str = Field(..., description="Main IATP JSON-RPC endpoint (usually at root path)")
|
|
71
|
+
streaming_endpoint: Optional[str] = Field(None, description="SSE streaming endpoint (same as iatp_endpoint when supported)")
|
|
72
|
+
health_endpoint: Optional[str] = Field(None, description="Health check endpoint (not part of A2A protocol)")
|
|
73
|
+
info_endpoint: Optional[str] = Field(None, description="Agent info endpoint (not part of A2A protocol)")
|
|
74
|
+
agent_card_endpoint: str = Field(..., description="Agent card endpoint (.well-known/agent.json)")
|
|
75
|
+
subscribe_endpoint: Optional[str] = Field(None, description="SSE subscription endpoint (/a2a/tasks/subscribe)")
|
|
76
|
+
resubscribe_endpoint: Optional[str] = Field(None, description="SSE resubscription endpoint (/a2a/tasks/resubscribe)")
|
|
77
|
+
|
|
78
|
+
|
|
79
|
+
class UtilityAgentStatus(str, Enum):
|
|
80
|
+
"""Status of a utility agent."""
|
|
81
|
+
GENERATED = "generated"
|
|
82
|
+
DEPLOYING = "deploying"
|
|
83
|
+
DEPLOYED = "deployed"
|
|
84
|
+
RUNNING = "running"
|
|
85
|
+
STOPPED = "stopped"
|
|
86
|
+
ERROR = "error"
|
|
87
|
+
|
|
88
|
+
|
|
89
|
+
#this is the model for internal management of utility agents by their creator and traia protocol (should be persisted into db)
|
|
90
|
+
class UtilityAgent(BaseModel):
|
|
91
|
+
"""Utility Agent configuration and metadata."""
|
|
92
|
+
id: str = Field(..., description="Unique identifier for the agent")
|
|
93
|
+
name: str = Field(..., description="Name of the utility agent")
|
|
94
|
+
description: str = Field(..., description="Description of the agent's purpose")
|
|
95
|
+
mcp_server_id: str = Field(..., description="ID of the associated MCP server")
|
|
96
|
+
|
|
97
|
+
# IATP specific fields
|
|
98
|
+
agent_card: Optional[AgentCard] = Field(None, description="IATP agent card for discovery")
|
|
99
|
+
endpoints: Optional[IATPEndpoints] = Field(None, description="IATP endpoints configuration")
|
|
100
|
+
|
|
101
|
+
capabilities: List[str] = Field(default_factory=list, description="List of exposed capabilities")
|
|
102
|
+
status: UtilityAgentStatus = Field(default=UtilityAgentStatus.GENERATED)
|
|
103
|
+
code_path: Optional[str] = Field(None, description="Path to generated code")
|
|
104
|
+
docker_image: Optional[str] = Field(None, description="Docker image name when built")
|
|
105
|
+
github_repo: Optional[str] = Field(None, description="GitHub repository URL")
|
|
106
|
+
cloud_run_url: Optional[str] = Field(None, description="Cloud Run deployment URL")
|
|
107
|
+
|
|
108
|
+
# X402 payment configuration
|
|
109
|
+
contract_address: Optional[str] = Field(None, description="On-chain utility agent contract address")
|
|
110
|
+
operator_address: Optional[str] = Field(None, description="Operator address for signing attestations")
|
|
111
|
+
d402_enabled: bool = Field(default=False, description="Whether d402 payments are enabled")
|
|
112
|
+
d402_config: Optional[Dict[str, Any]] = Field(None, description="X402 payment configuration")
|
|
113
|
+
|
|
114
|
+
# Search and discovery
|
|
115
|
+
search_text: Optional[str] = Field(None, description="Concatenated searchable text")
|
|
116
|
+
tags: List[str] = Field(default_factory=list, description="Tags for search and categorization")
|
|
117
|
+
|
|
118
|
+
created_at: datetime = Field(default_factory=datetime.utcnow)
|
|
119
|
+
updated_at: datetime = Field(default_factory=datetime.utcnow)
|
|
120
|
+
metadata: Dict[str, Any] = Field(default_factory=dict, description="Additional metadata")
|
|
121
|
+
|
|
122
|
+
class ConfigDict:
|
|
123
|
+
json_encoders = {HttpUrl: str, datetime: lambda v: v.isoformat()}
|
|
124
|
+
|
|
125
|
+
|
|
126
|
+
#this is a model for registry discovery that will be used by the mongodb indexes
|
|
127
|
+
class UtilityAgentRegistryEntry(BaseModel):
|
|
128
|
+
"""Registry entry for a deployed utility agent."""
|
|
129
|
+
agent_id: str = Field(..., description="ID of the utility agent")
|
|
130
|
+
name: str = Field(..., description="Name for discovery")
|
|
131
|
+
description: str = Field(..., description="Description for search")
|
|
132
|
+
|
|
133
|
+
# Base URL for the agent - all endpoints are derived from this
|
|
134
|
+
base_url: Optional[str] = Field(None, description="Base URL of the deployed agent")
|
|
135
|
+
|
|
136
|
+
# Enhanced IATP discovery fields
|
|
137
|
+
agent_card: Optional[AgentCard] = Field(None, description="IATP agent card")
|
|
138
|
+
endpoints: Optional[IATPEndpoints] = Field(None, description="IATP endpoints")
|
|
139
|
+
|
|
140
|
+
capabilities: List[str] = Field(..., description="List of capabilities")
|
|
141
|
+
skills: List[AgentSkill] = Field(default_factory=list, description="Detailed skills from agent card")
|
|
142
|
+
tags: List[str] = Field(default_factory=list, description="Tags for search")
|
|
143
|
+
|
|
144
|
+
# X402 payment information
|
|
145
|
+
contract_address: Optional[str] = Field(None, description="On-chain utility agent contract address")
|
|
146
|
+
d402_enabled: bool = Field(default=False, description="Whether d402 payments are enabled")
|
|
147
|
+
d402_payment_info: Optional[Dict[str, Any]] = Field(None, description="X402 payment information")
|
|
148
|
+
|
|
149
|
+
# Search optimization
|
|
150
|
+
search_text: Optional[str] = Field(None, description="Full text for search")
|
|
151
|
+
|
|
152
|
+
metadata: Dict[str, Any] = Field(default_factory=dict, description="Additional metadata")
|
|
153
|
+
registered_at: datetime = Field(default_factory=datetime.utcnow)
|
|
154
|
+
last_health_check: Optional[datetime] = Field(None, description="Last successful health check")
|
|
155
|
+
is_active: bool = Field(default=True, description="Whether the agent is active")
|
|
156
|
+
|
|
157
|
+
class ConfigDict:
|
|
158
|
+
json_encoders = {HttpUrl: str, datetime: lambda v: v.isoformat()}
|
|
159
|
+
|
|
160
|
+
|
|
161
|
+
class IATPRequest(BaseModel):
|
|
162
|
+
"""IATP protocol request."""
|
|
163
|
+
action: str = Field(..., description="Action to perform")
|
|
164
|
+
parameters: Dict[str, Any] = Field(default_factory=dict, description="Action parameters")
|
|
165
|
+
context: Dict[str, Any] = Field(default_factory=dict, description="Request context")
|
|
166
|
+
|
|
167
|
+
|
|
168
|
+
class IATPResponse(BaseModel):
|
|
169
|
+
"""IATP protocol response."""
|
|
170
|
+
result: Any = Field(..., description="Result of the action")
|
|
171
|
+
status: str = Field(..., description="Status of the response")
|
|
172
|
+
metadata: Dict[str, Any] = Field(default_factory=dict, description="Response metadata")
|
|
@@ -0,0 +1,55 @@
|
|
|
1
|
+
"""D402 payment integration for IATP protocol.
|
|
2
|
+
|
|
3
|
+
This module provides d402 (HTTP 402 Payment Required) payment capabilities
|
|
4
|
+
for the Inter-Agent Transfer Protocol (IATP). It enables utility agents to
|
|
5
|
+
accept payments and client agents to send payments for API access.
|
|
6
|
+
|
|
7
|
+
Components:
|
|
8
|
+
- middleware: FastAPI middleware for accepting d402 payments
|
|
9
|
+
- client: d402 client integration for sending payments
|
|
10
|
+
- facilitator: Custom facilitator that interfaces with IATPSettlementLayer
|
|
11
|
+
- models: Payment configuration models
|
|
12
|
+
"""
|
|
13
|
+
|
|
14
|
+
from .models import (
|
|
15
|
+
D402Config,
|
|
16
|
+
D402PaymentInfo,
|
|
17
|
+
D402ServicePrice,
|
|
18
|
+
PaymentScheme,
|
|
19
|
+
)
|
|
20
|
+
from .middleware import D402IATPMiddleware, require_iatp_payment
|
|
21
|
+
from .client import D402IATPClient
|
|
22
|
+
from .facilitator import IATPSettlementFacilitator
|
|
23
|
+
from .fastmcp_middleware import (
|
|
24
|
+
D402MCPMiddleware,
|
|
25
|
+
create_d402_mcp_middleware,
|
|
26
|
+
)
|
|
27
|
+
from .mcp_middleware import (
|
|
28
|
+
EndpointPaymentInfo,
|
|
29
|
+
get_active_api_key,
|
|
30
|
+
require_payment_for_tool,
|
|
31
|
+
settle_payment
|
|
32
|
+
)
|
|
33
|
+
from .clients.httpx import d402_payment_hooks, d402HttpxClient
|
|
34
|
+
from .clients.base import decode_x_payment_response
|
|
35
|
+
|
|
36
|
+
__all__ = [
|
|
37
|
+
"D402Config",
|
|
38
|
+
"D402PaymentInfo",
|
|
39
|
+
"D402ServicePrice",
|
|
40
|
+
"PaymentScheme",
|
|
41
|
+
"D402IATPMiddleware",
|
|
42
|
+
"require_iatp_payment",
|
|
43
|
+
"D402IATPClient",
|
|
44
|
+
"IATPSettlementFacilitator",
|
|
45
|
+
"D402MCPMiddleware",
|
|
46
|
+
"create_d402_mcp_middleware",
|
|
47
|
+
"EndpointPaymentInfo",
|
|
48
|
+
"get_active_api_key",
|
|
49
|
+
"require_payment_for_tool",
|
|
50
|
+
"settle_payment",
|
|
51
|
+
"d402_payment_hooks",
|
|
52
|
+
"d402HttpxClient",
|
|
53
|
+
"decode_x_payment_response",
|
|
54
|
+
]
|
|
55
|
+
|
|
@@ -0,0 +1,102 @@
|
|
|
1
|
+
NETWORK_TO_ID = {
|
|
2
|
+
"sepolia": "11155111", # Ethereum Sepolia testnet
|
|
3
|
+
"base-sepolia": "84532",
|
|
4
|
+
"base": "8453",
|
|
5
|
+
"avalanche-fuji": "43113",
|
|
6
|
+
"avalanche": "43114",
|
|
7
|
+
}
|
|
8
|
+
|
|
9
|
+
|
|
10
|
+
def get_chain_id(network: str) -> str:
|
|
11
|
+
"""Get the chain ID for a given network
|
|
12
|
+
Supports string encoded chain IDs and human readable networks
|
|
13
|
+
"""
|
|
14
|
+
try:
|
|
15
|
+
int(network)
|
|
16
|
+
return network
|
|
17
|
+
except ValueError:
|
|
18
|
+
pass
|
|
19
|
+
if network not in NETWORK_TO_ID:
|
|
20
|
+
raise ValueError(f"Unsupported network: {network}")
|
|
21
|
+
return NETWORK_TO_ID[network]
|
|
22
|
+
|
|
23
|
+
|
|
24
|
+
KNOWN_TOKENS = {
|
|
25
|
+
"11155111": [ # Sepolia testnet
|
|
26
|
+
{
|
|
27
|
+
"human_name": "usdc",
|
|
28
|
+
"address": "0x036CbD53842c5426634e7929541eC2318f3dCF7e",
|
|
29
|
+
"name": "USD Coin",
|
|
30
|
+
"decimals": 6,
|
|
31
|
+
"version": "2",
|
|
32
|
+
}
|
|
33
|
+
],
|
|
34
|
+
"84532": [
|
|
35
|
+
{
|
|
36
|
+
"human_name": "usdc",
|
|
37
|
+
"address": "0x036CbD53842c5426634e7929541eC2318f3dCF7e",
|
|
38
|
+
"name": "USDC",
|
|
39
|
+
"decimals": 6,
|
|
40
|
+
"version": "2",
|
|
41
|
+
}
|
|
42
|
+
],
|
|
43
|
+
"8453": [
|
|
44
|
+
{
|
|
45
|
+
"human_name": "usdc",
|
|
46
|
+
"address": "0x833589fCD6eDb6E08f4c7C32D4f71b54bdA02913",
|
|
47
|
+
"name": "USD Coin", # needs to be exactly what is returned by name() on contract
|
|
48
|
+
"decimals": 6,
|
|
49
|
+
"version": "2",
|
|
50
|
+
}
|
|
51
|
+
],
|
|
52
|
+
"43113": [
|
|
53
|
+
{
|
|
54
|
+
"human_name": "usdc",
|
|
55
|
+
"address": "0x5425890298aed601595a70AB815c96711a31Bc65",
|
|
56
|
+
"name": "USD Coin",
|
|
57
|
+
"decimals": 6,
|
|
58
|
+
"version": "2",
|
|
59
|
+
}
|
|
60
|
+
],
|
|
61
|
+
"43114": [
|
|
62
|
+
{
|
|
63
|
+
"human_name": "usdc",
|
|
64
|
+
"address": "0xB97EF9Ef8734C71904D8002F8b6Bc66Dd9c48a6E",
|
|
65
|
+
"name": "USDC",
|
|
66
|
+
"decimals": 6,
|
|
67
|
+
"version": "2",
|
|
68
|
+
}
|
|
69
|
+
],
|
|
70
|
+
}
|
|
71
|
+
|
|
72
|
+
|
|
73
|
+
def get_token_name(chain_id: str, address: str) -> str:
|
|
74
|
+
"""Get the token name for a given chain and address"""
|
|
75
|
+
for token in KNOWN_TOKENS[chain_id]:
|
|
76
|
+
if token["address"] == address:
|
|
77
|
+
return token["name"]
|
|
78
|
+
raise ValueError(f"Token not found for chain {chain_id} and address {address}")
|
|
79
|
+
|
|
80
|
+
|
|
81
|
+
def get_token_version(chain_id: str, address: str) -> str:
|
|
82
|
+
"""Get the token version for a given chain and address"""
|
|
83
|
+
for token in KNOWN_TOKENS[chain_id]:
|
|
84
|
+
if token["address"] == address:
|
|
85
|
+
return token["version"]
|
|
86
|
+
raise ValueError(f"Token not found for chain {chain_id} and address {address}")
|
|
87
|
+
|
|
88
|
+
|
|
89
|
+
def get_token_decimals(chain_id: str, address: str) -> int:
|
|
90
|
+
"""Get the token decimals for a given chain and address"""
|
|
91
|
+
for token in KNOWN_TOKENS[chain_id]:
|
|
92
|
+
if token["address"] == address:
|
|
93
|
+
return token["decimals"]
|
|
94
|
+
raise ValueError(f"Token not found for chain {chain_id} and address {address}")
|
|
95
|
+
|
|
96
|
+
|
|
97
|
+
def get_default_token_address(chain_id: str, token_type: str = "usdc") -> str:
|
|
98
|
+
"""Get the default token address for a given chain and token type"""
|
|
99
|
+
for token in KNOWN_TOKENS[chain_id]:
|
|
100
|
+
if token["human_name"] == token_type:
|
|
101
|
+
return token["address"]
|
|
102
|
+
raise ValueError(f"Token type '{token_type}' not found for chain {chain_id}")
|
|
@@ -0,0 +1,150 @@
|
|
|
1
|
+
"""D402 client for IATP agent-to-agent payments."""
|
|
2
|
+
|
|
3
|
+
import logging
|
|
4
|
+
from typing import Optional, Dict, Any
|
|
5
|
+
from eth_account import Account
|
|
6
|
+
from .clients.base import d402Client
|
|
7
|
+
from .types import PaymentRequirements
|
|
8
|
+
|
|
9
|
+
logger = logging.getLogger(__name__)
|
|
10
|
+
|
|
11
|
+
|
|
12
|
+
class D402IATPClient:
|
|
13
|
+
"""Client for making d402 payments in IATP protocol.
|
|
14
|
+
|
|
15
|
+
This wraps the Coinbase d402 client and provides IATP-specific functionality,
|
|
16
|
+
including integration with utility agent smart contracts.
|
|
17
|
+
"""
|
|
18
|
+
|
|
19
|
+
def __init__(
|
|
20
|
+
self,
|
|
21
|
+
account: Account,
|
|
22
|
+
max_value: Optional[int] = None,
|
|
23
|
+
agent_contract_address: Optional[str] = None,
|
|
24
|
+
operator_private_key: Optional[str] = None
|
|
25
|
+
):
|
|
26
|
+
"""Initialize the d402 IATP client.
|
|
27
|
+
|
|
28
|
+
Args:
|
|
29
|
+
account: eth_account.Account instance for signing payments
|
|
30
|
+
max_value: Optional maximum allowed payment amount in base units
|
|
31
|
+
agent_contract_address: Optional address of the client agent's smart contract
|
|
32
|
+
operator_private_key: Optional operator private key for signing service requests
|
|
33
|
+
"""
|
|
34
|
+
self.account = account
|
|
35
|
+
self.max_value = max_value
|
|
36
|
+
self.agent_contract_address = agent_contract_address
|
|
37
|
+
self.operator_private_key = operator_private_key
|
|
38
|
+
|
|
39
|
+
# Initialize the underlying d402 client
|
|
40
|
+
self.d402_client = d402Client(
|
|
41
|
+
account=account,
|
|
42
|
+
max_value=max_value
|
|
43
|
+
)
|
|
44
|
+
|
|
45
|
+
def create_payment_header(
|
|
46
|
+
self,
|
|
47
|
+
payment_requirements: PaymentRequirements,
|
|
48
|
+
d402_version: int = 1
|
|
49
|
+
) -> str:
|
|
50
|
+
"""Create a payment header for the given requirements.
|
|
51
|
+
|
|
52
|
+
This creates an EIP-3009 signed payment authorization that the facilitator
|
|
53
|
+
can use to pull funds from the client agent's wallet.
|
|
54
|
+
|
|
55
|
+
Args:
|
|
56
|
+
payment_requirements: Selected payment requirements from server
|
|
57
|
+
d402_version: d402 protocol version
|
|
58
|
+
|
|
59
|
+
Returns:
|
|
60
|
+
Base64-encoded signed payment header for X-PAYMENT header
|
|
61
|
+
"""
|
|
62
|
+
return self.d402_client.create_payment_header(
|
|
63
|
+
payment_requirements=payment_requirements,
|
|
64
|
+
d402_version=d402_version
|
|
65
|
+
)
|
|
66
|
+
|
|
67
|
+
def select_payment_requirements(
|
|
68
|
+
self,
|
|
69
|
+
accepts: list[PaymentRequirements],
|
|
70
|
+
network_filter: Optional[str] = None,
|
|
71
|
+
scheme_filter: Optional[str] = "exact"
|
|
72
|
+
) -> PaymentRequirements:
|
|
73
|
+
"""Select payment requirements from available options.
|
|
74
|
+
|
|
75
|
+
Args:
|
|
76
|
+
accepts: List of accepted payment requirements from server
|
|
77
|
+
network_filter: Optional network to filter by
|
|
78
|
+
scheme_filter: Optional scheme to filter by (default: "exact")
|
|
79
|
+
|
|
80
|
+
Returns:
|
|
81
|
+
Selected payment requirements
|
|
82
|
+
|
|
83
|
+
Raises:
|
|
84
|
+
UnsupportedSchemeException: If no supported scheme found
|
|
85
|
+
PaymentAmountExceededError: If amount exceeds max_value
|
|
86
|
+
"""
|
|
87
|
+
return self.d402_client.select_payment_requirements(
|
|
88
|
+
accepts=accepts,
|
|
89
|
+
network_filter=network_filter,
|
|
90
|
+
scheme_filter=scheme_filter
|
|
91
|
+
)
|
|
92
|
+
|
|
93
|
+
def get_payment_info_for_agent_card(self, agent_card: dict) -> Optional[Dict[str, Any]]:
|
|
94
|
+
"""Extract d402 payment information from an agent card.
|
|
95
|
+
|
|
96
|
+
Args:
|
|
97
|
+
agent_card: Agent card dictionary
|
|
98
|
+
|
|
99
|
+
Returns:
|
|
100
|
+
Payment information if available, None otherwise
|
|
101
|
+
"""
|
|
102
|
+
metadata = agent_card.get("metadata", {})
|
|
103
|
+
return metadata.get("d402")
|
|
104
|
+
|
|
105
|
+
|
|
106
|
+
def create_iatp_payment_client(
|
|
107
|
+
private_key: str,
|
|
108
|
+
max_value_usd: Optional[float] = None,
|
|
109
|
+
agent_contract_address: Optional[str] = None
|
|
110
|
+
) -> D402IATPClient:
|
|
111
|
+
"""Convenience function to create an IATP payment client.
|
|
112
|
+
|
|
113
|
+
Args:
|
|
114
|
+
private_key: Hex-encoded private key (with or without 0x prefix)
|
|
115
|
+
max_value_usd: Optional maximum payment in USD
|
|
116
|
+
agent_contract_address: Optional agent contract address
|
|
117
|
+
|
|
118
|
+
Returns:
|
|
119
|
+
Configured D402IATPClient
|
|
120
|
+
|
|
121
|
+
Example:
|
|
122
|
+
client = create_iatp_payment_client(
|
|
123
|
+
private_key="0x...",
|
|
124
|
+
max_value_usd=10.0 # Max $10 per request
|
|
125
|
+
)
|
|
126
|
+
|
|
127
|
+
# Use with httpx
|
|
128
|
+
from .clients.httpx import Httpd402Client
|
|
129
|
+
http_client = Httpd402Client(client)
|
|
130
|
+
response = await http_client.get("https://agent.example.com/api")
|
|
131
|
+
"""
|
|
132
|
+
# Remove 0x prefix if present
|
|
133
|
+
if private_key.startswith("0x"):
|
|
134
|
+
private_key = private_key[2:]
|
|
135
|
+
|
|
136
|
+
# Create eth_account.Account
|
|
137
|
+
account = Account.from_key(private_key)
|
|
138
|
+
|
|
139
|
+
# Convert USD to atomic units (assuming USDC with 6 decimals)
|
|
140
|
+
max_value = None
|
|
141
|
+
if max_value_usd is not None:
|
|
142
|
+
max_value = int(max_value_usd * 1_000_000) # USDC has 6 decimals
|
|
143
|
+
|
|
144
|
+
return D402IATPClient(
|
|
145
|
+
account=account,
|
|
146
|
+
max_value=max_value,
|
|
147
|
+
agent_contract_address=agent_contract_address,
|
|
148
|
+
operator_private_key=private_key
|
|
149
|
+
)
|
|
150
|
+
|