agentspay 0.1.0 → 0.2.0
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.
- package/.env.example +40 -0
- package/README.md +35 -4
- package/VERIFICATION_SUMMARY.txt +151 -0
- package/dist/api/server.d.ts.map +1 -1
- package/dist/api/server.js +530 -74
- package/dist/api/server.js.map +1 -1
- package/dist/bsv/crypto.d.ts +65 -0
- package/dist/bsv/crypto.d.ts.map +1 -0
- package/dist/bsv/crypto.js +158 -0
- package/dist/bsv/crypto.js.map +1 -0
- package/dist/bsv/mnee.d.ts +88 -0
- package/dist/bsv/mnee.d.ts.map +1 -0
- package/dist/bsv/mnee.js +173 -0
- package/dist/bsv/mnee.js.map +1 -0
- package/dist/bsv/opreturn.d.ts +22 -0
- package/dist/bsv/opreturn.d.ts.map +1 -0
- package/dist/bsv/opreturn.js +117 -0
- package/dist/bsv/opreturn.js.map +1 -0
- package/dist/bsv/whatsonchain.d.ts +46 -0
- package/dist/bsv/whatsonchain.d.ts.map +1 -0
- package/dist/bsv/whatsonchain.js +98 -0
- package/dist/bsv/whatsonchain.js.map +1 -0
- package/dist/config.d.ts +38 -0
- package/dist/config.d.ts.map +1 -0
- package/dist/config.js +85 -0
- package/dist/config.js.map +1 -0
- package/dist/currency/currency.d.ts +70 -0
- package/dist/currency/currency.d.ts.map +1 -0
- package/dist/currency/currency.js +137 -0
- package/dist/currency/currency.js.map +1 -0
- package/dist/disputes/dispute.d.ts +50 -0
- package/dist/disputes/dispute.d.ts.map +1 -0
- package/dist/disputes/dispute.js +162 -0
- package/dist/disputes/dispute.js.map +1 -0
- package/dist/docs/openapi.yaml +1079 -0
- package/dist/docs/swagger.d.ts +12 -0
- package/dist/docs/swagger.d.ts.map +1 -0
- package/dist/docs/swagger.js +104 -0
- package/dist/docs/swagger.js.map +1 -0
- package/dist/middleware/auth.d.ts +20 -0
- package/dist/middleware/auth.d.ts.map +1 -0
- package/dist/middleware/auth.js +32 -0
- package/dist/middleware/auth.js.map +1 -0
- package/dist/middleware/rateLimit.d.ts +25 -0
- package/dist/middleware/rateLimit.d.ts.map +1 -0
- package/dist/middleware/rateLimit.js +61 -0
- package/dist/middleware/rateLimit.js.map +1 -0
- package/dist/payment/payment.d.ts +79 -9
- package/dist/payment/payment.d.ts.map +1 -1
- package/dist/payment/payment.js +387 -47
- package/dist/payment/payment.js.map +1 -1
- package/dist/registry/db.d.ts.map +1 -1
- package/dist/registry/db.js +110 -3
- package/dist/registry/db.js.map +1 -1
- package/dist/registry/registry.d.ts +1 -1
- package/dist/registry/registry.d.ts.map +1 -1
- package/dist/registry/registry.js +12 -4
- package/dist/registry/registry.js.map +1 -1
- package/dist/types/index.d.ts +34 -0
- package/dist/types/index.d.ts.map +1 -1
- package/dist/types/index.js.map +1 -1
- package/dist/utils/validation.d.ts +27 -0
- package/dist/utils/validation.d.ts.map +1 -0
- package/dist/utils/validation.js +164 -0
- package/dist/utils/validation.js.map +1 -0
- package/dist/verification/receipt.d.ts +42 -0
- package/dist/verification/receipt.d.ts.map +1 -0
- package/dist/verification/receipt.js +10 -0
- package/dist/verification/receipt.js.map +1 -0
- package/dist/verification/verification.d.ts +41 -0
- package/dist/verification/verification.d.ts.map +1 -0
- package/dist/verification/verification.js +217 -0
- package/dist/verification/verification.js.map +1 -0
- package/dist/wallet/providerManager.d.ts +32 -0
- package/dist/wallet/providerManager.d.ts.map +1 -0
- package/dist/wallet/providerManager.js +118 -0
- package/dist/wallet/providerManager.js.map +1 -0
- package/dist/wallet/providers/handcash.d.ts +22 -0
- package/dist/wallet/providers/handcash.d.ts.map +1 -0
- package/dist/wallet/providers/handcash.js +214 -0
- package/dist/wallet/providers/handcash.js.map +1 -0
- package/dist/wallet/providers/internal.d.ts +15 -0
- package/dist/wallet/providers/internal.d.ts.map +1 -0
- package/dist/wallet/providers/internal.js +208 -0
- package/dist/wallet/providers/internal.js.map +1 -0
- package/dist/wallet/providers/types.d.ts +50 -0
- package/dist/wallet/providers/types.d.ts.map +1 -0
- package/dist/wallet/providers/types.js +6 -0
- package/dist/wallet/providers/types.js.map +1 -0
- package/dist/wallet/providers/yours.d.ts +18 -0
- package/dist/wallet/providers/yours.d.ts.map +1 -0
- package/dist/wallet/providers/yours.js +122 -0
- package/dist/wallet/providers/yours.js.map +1 -0
- package/dist/wallet/wallet.d.ts +52 -5
- package/dist/wallet/wallet.d.ts.map +1 -1
- package/dist/wallet/wallet.js +223 -34
- package/dist/wallet/wallet.js.map +1 -1
- package/dist/webhooks/delivery.d.ts +37 -0
- package/dist/webhooks/delivery.d.ts.map +1 -0
- package/dist/webhooks/delivery.js +182 -0
- package/dist/webhooks/delivery.js.map +1 -0
- package/dist/webhooks/webhook.d.ts +85 -0
- package/dist/webhooks/webhook.d.ts.map +1 -0
- package/dist/webhooks/webhook.js +271 -0
- package/dist/webhooks/webhook.js.map +1 -0
- package/package.json +74 -54
- package/sdk-python/LICENSE +21 -0
- package/sdk-python/MANIFEST.in +9 -0
- package/sdk-python/README.md +372 -0
- package/sdk-python/agentspay/__init__.py +97 -0
- package/sdk-python/agentspay/client.py +256 -0
- package/sdk-python/agentspay/disputes.py +174 -0
- package/sdk-python/agentspay/exceptions.py +53 -0
- package/sdk-python/agentspay/payments.py +169 -0
- package/sdk-python/agentspay/services.py +198 -0
- package/sdk-python/agentspay/types.py +154 -0
- package/sdk-python/agentspay/wallet.py +113 -0
- package/sdk-python/agentspay/webhooks.py +195 -0
- package/sdk-python/examples/consumer.py +147 -0
- package/sdk-python/examples/provider.py +116 -0
- package/sdk-python/pyproject.toml +61 -0
- package/sdk-python/setup.py +53 -0
- package/sdk-python/tests/test_client.py +221 -0
- package/test-addr.js +29 -0
- package/test-mnee-simple.js +51 -0
- package/test-mnee.js +47 -0
|
@@ -0,0 +1,198 @@
|
|
|
1
|
+
"""Service operations for AgentPay SDK"""
|
|
2
|
+
|
|
3
|
+
from typing import Optional, List
|
|
4
|
+
import requests
|
|
5
|
+
from .types import Service, ServiceQuery
|
|
6
|
+
from .exceptions import ServiceError
|
|
7
|
+
|
|
8
|
+
|
|
9
|
+
class ServiceOperations:
|
|
10
|
+
"""Handles service registration and discovery"""
|
|
11
|
+
|
|
12
|
+
def __init__(self, base_url: str, api_key: Optional[str] = None):
|
|
13
|
+
self.base_url = base_url
|
|
14
|
+
self.api_key = api_key
|
|
15
|
+
|
|
16
|
+
def _get_headers(self) -> dict:
|
|
17
|
+
"""Get request headers with API key if available"""
|
|
18
|
+
headers = {"Content-Type": "application/json"}
|
|
19
|
+
if self.api_key:
|
|
20
|
+
headers["Authorization"] = f"Bearer {self.api_key}"
|
|
21
|
+
return headers
|
|
22
|
+
|
|
23
|
+
def register_service(
|
|
24
|
+
self,
|
|
25
|
+
agent_id: str,
|
|
26
|
+
name: str,
|
|
27
|
+
description: str,
|
|
28
|
+
price: int,
|
|
29
|
+
endpoint: str,
|
|
30
|
+
category: str = "general",
|
|
31
|
+
currency: str = "BSV",
|
|
32
|
+
method: str = "POST",
|
|
33
|
+
timeout: int = 30,
|
|
34
|
+
dispute_window: int = 30,
|
|
35
|
+
input_schema: Optional[dict] = None,
|
|
36
|
+
output_schema: Optional[dict] = None
|
|
37
|
+
) -> Service:
|
|
38
|
+
"""
|
|
39
|
+
Register a new service
|
|
40
|
+
|
|
41
|
+
Args:
|
|
42
|
+
agent_id: Provider wallet ID
|
|
43
|
+
name: Service name
|
|
44
|
+
description: Service description
|
|
45
|
+
price: Price per execution (satoshis for BSV, cents for MNEE)
|
|
46
|
+
endpoint: Service endpoint URL
|
|
47
|
+
category: Service category
|
|
48
|
+
currency: "BSV" or "MNEE"
|
|
49
|
+
method: HTTP method ("POST" or "GET")
|
|
50
|
+
timeout: Max execution time in seconds
|
|
51
|
+
dispute_window: Dispute window in minutes
|
|
52
|
+
input_schema: Optional JSON schema for input validation
|
|
53
|
+
output_schema: Optional JSON schema for output validation
|
|
54
|
+
|
|
55
|
+
Returns:
|
|
56
|
+
Service: The registered service
|
|
57
|
+
|
|
58
|
+
Raises:
|
|
59
|
+
ServiceError: If registration fails
|
|
60
|
+
"""
|
|
61
|
+
payload = {
|
|
62
|
+
"agentId": agent_id,
|
|
63
|
+
"name": name,
|
|
64
|
+
"description": description,
|
|
65
|
+
"category": category,
|
|
66
|
+
"price": price,
|
|
67
|
+
"currency": currency.upper(),
|
|
68
|
+
"endpoint": endpoint,
|
|
69
|
+
"method": method.upper(),
|
|
70
|
+
"timeout": timeout,
|
|
71
|
+
"disputeWindow": dispute_window
|
|
72
|
+
}
|
|
73
|
+
|
|
74
|
+
if input_schema:
|
|
75
|
+
payload["inputSchema"] = input_schema
|
|
76
|
+
if output_schema:
|
|
77
|
+
payload["outputSchema"] = output_schema
|
|
78
|
+
|
|
79
|
+
try:
|
|
80
|
+
response = requests.post(
|
|
81
|
+
f"{self.base_url}/api/services",
|
|
82
|
+
json=payload,
|
|
83
|
+
headers=self._get_headers()
|
|
84
|
+
)
|
|
85
|
+
response.raise_for_status()
|
|
86
|
+
data = response.json()
|
|
87
|
+
|
|
88
|
+
service_data = data.get("service")
|
|
89
|
+
if not service_data:
|
|
90
|
+
raise ServiceError("Invalid response: missing service data")
|
|
91
|
+
|
|
92
|
+
return self._parse_service(service_data)
|
|
93
|
+
except requests.RequestException as e:
|
|
94
|
+
raise ServiceError(f"Failed to register service: {str(e)}") from e
|
|
95
|
+
|
|
96
|
+
def search_services(
|
|
97
|
+
self,
|
|
98
|
+
keyword: Optional[str] = None,
|
|
99
|
+
category: Optional[str] = None,
|
|
100
|
+
max_price: Optional[int] = None,
|
|
101
|
+
min_rating: Optional[float] = None,
|
|
102
|
+
limit: int = 20,
|
|
103
|
+
offset: int = 0
|
|
104
|
+
) -> List[Service]:
|
|
105
|
+
"""
|
|
106
|
+
Search for services
|
|
107
|
+
|
|
108
|
+
Args:
|
|
109
|
+
keyword: Search keyword
|
|
110
|
+
category: Filter by category
|
|
111
|
+
max_price: Maximum price filter
|
|
112
|
+
min_rating: Minimum rating filter
|
|
113
|
+
limit: Max results to return
|
|
114
|
+
offset: Result offset for pagination
|
|
115
|
+
|
|
116
|
+
Returns:
|
|
117
|
+
List[Service]: List of matching services
|
|
118
|
+
|
|
119
|
+
Raises:
|
|
120
|
+
ServiceError: If search fails
|
|
121
|
+
"""
|
|
122
|
+
params = {}
|
|
123
|
+
if keyword:
|
|
124
|
+
params["q"] = keyword
|
|
125
|
+
if category:
|
|
126
|
+
params["category"] = category
|
|
127
|
+
if max_price is not None:
|
|
128
|
+
params["maxPrice"] = str(max_price)
|
|
129
|
+
if min_rating is not None:
|
|
130
|
+
params["minRating"] = str(min_rating)
|
|
131
|
+
if limit:
|
|
132
|
+
params["limit"] = str(limit)
|
|
133
|
+
if offset:
|
|
134
|
+
params["offset"] = str(offset)
|
|
135
|
+
|
|
136
|
+
try:
|
|
137
|
+
response = requests.get(
|
|
138
|
+
f"{self.base_url}/api/services",
|
|
139
|
+
params=params,
|
|
140
|
+
headers=self._get_headers()
|
|
141
|
+
)
|
|
142
|
+
response.raise_for_status()
|
|
143
|
+
data = response.json()
|
|
144
|
+
|
|
145
|
+
services_data = data.get("services", [])
|
|
146
|
+
return [self._parse_service(s) for s in services_data]
|
|
147
|
+
except requests.RequestException as e:
|
|
148
|
+
raise ServiceError(f"Failed to search services: {str(e)}") from e
|
|
149
|
+
|
|
150
|
+
def get_service(self, service_id: str) -> Service:
|
|
151
|
+
"""
|
|
152
|
+
Get service by ID
|
|
153
|
+
|
|
154
|
+
Args:
|
|
155
|
+
service_id: Service ID
|
|
156
|
+
|
|
157
|
+
Returns:
|
|
158
|
+
Service: The service
|
|
159
|
+
|
|
160
|
+
Raises:
|
|
161
|
+
ServiceError: If service not found or request fails
|
|
162
|
+
"""
|
|
163
|
+
try:
|
|
164
|
+
response = requests.get(
|
|
165
|
+
f"{self.base_url}/api/services/{service_id}",
|
|
166
|
+
headers=self._get_headers()
|
|
167
|
+
)
|
|
168
|
+
response.raise_for_status()
|
|
169
|
+
data = response.json()
|
|
170
|
+
|
|
171
|
+
service_data = data.get("service")
|
|
172
|
+
if not service_data:
|
|
173
|
+
raise ServiceError(f"Service {service_id} not found")
|
|
174
|
+
|
|
175
|
+
return self._parse_service(service_data)
|
|
176
|
+
except requests.RequestException as e:
|
|
177
|
+
raise ServiceError(f"Failed to get service: {str(e)}") from e
|
|
178
|
+
|
|
179
|
+
def _parse_service(self, data: dict) -> Service:
|
|
180
|
+
"""Parse service data from API response"""
|
|
181
|
+
return Service(
|
|
182
|
+
id=data["id"],
|
|
183
|
+
agent_id=data["agentId"],
|
|
184
|
+
name=data["name"],
|
|
185
|
+
description=data["description"],
|
|
186
|
+
category=data["category"],
|
|
187
|
+
price=data["price"],
|
|
188
|
+
currency=data["currency"],
|
|
189
|
+
endpoint=data["endpoint"],
|
|
190
|
+
method=data["method"],
|
|
191
|
+
active=data["active"],
|
|
192
|
+
timeout=data.get("timeout", 30),
|
|
193
|
+
dispute_window=data.get("disputeWindow", 30),
|
|
194
|
+
created_at=data["createdAt"],
|
|
195
|
+
updated_at=data["updatedAt"],
|
|
196
|
+
input_schema=data.get("inputSchema"),
|
|
197
|
+
output_schema=data.get("outputSchema")
|
|
198
|
+
)
|
|
@@ -0,0 +1,154 @@
|
|
|
1
|
+
"""AgentPay SDK Type Definitions"""
|
|
2
|
+
|
|
3
|
+
from dataclasses import dataclass, field
|
|
4
|
+
from typing import Optional, Literal, Dict, Any
|
|
5
|
+
from datetime import datetime
|
|
6
|
+
|
|
7
|
+
|
|
8
|
+
Currency = Literal["BSV", "MNEE"]
|
|
9
|
+
PaymentStatus = Literal["pending", "escrowed", "released", "disputed", "refunded"]
|
|
10
|
+
DisputeStatus = Literal["open", "under_review", "resolved_refund", "resolved_release", "resolved_split", "expired"]
|
|
11
|
+
DisputeResolution = Literal["refund", "release", "split"]
|
|
12
|
+
ExecutionStatus = Literal["success", "error"]
|
|
13
|
+
HttpMethod = Literal["POST", "GET"]
|
|
14
|
+
|
|
15
|
+
|
|
16
|
+
@dataclass
|
|
17
|
+
class AgentWallet:
|
|
18
|
+
"""Agent wallet representation"""
|
|
19
|
+
id: str
|
|
20
|
+
public_key: str
|
|
21
|
+
address: str
|
|
22
|
+
created_at: str
|
|
23
|
+
balance: Optional[int] = None # satoshis (BSV)
|
|
24
|
+
balance_mnee: Optional[int] = None # cents (MNEE)
|
|
25
|
+
|
|
26
|
+
|
|
27
|
+
@dataclass
|
|
28
|
+
class Service:
|
|
29
|
+
"""Service registration"""
|
|
30
|
+
id: str
|
|
31
|
+
agent_id: str
|
|
32
|
+
name: str
|
|
33
|
+
description: str
|
|
34
|
+
category: str
|
|
35
|
+
price: int # satoshis for BSV, cents for MNEE
|
|
36
|
+
currency: Currency
|
|
37
|
+
endpoint: str
|
|
38
|
+
method: HttpMethod
|
|
39
|
+
active: bool
|
|
40
|
+
timeout: int # seconds
|
|
41
|
+
dispute_window: int # minutes
|
|
42
|
+
created_at: str
|
|
43
|
+
updated_at: str
|
|
44
|
+
input_schema: Optional[Dict[str, Any]] = None
|
|
45
|
+
output_schema: Optional[Dict[str, Any]] = None
|
|
46
|
+
|
|
47
|
+
|
|
48
|
+
@dataclass
|
|
49
|
+
class Payment:
|
|
50
|
+
"""Payment record"""
|
|
51
|
+
id: str
|
|
52
|
+
service_id: str
|
|
53
|
+
buyer_wallet_id: str
|
|
54
|
+
seller_wallet_id: str
|
|
55
|
+
amount: int
|
|
56
|
+
platform_fee: int
|
|
57
|
+
currency: Currency
|
|
58
|
+
status: PaymentStatus
|
|
59
|
+
created_at: str
|
|
60
|
+
dispute_status: Optional[str] = None
|
|
61
|
+
tx_id: Optional[str] = None
|
|
62
|
+
completed_at: Optional[str] = None
|
|
63
|
+
|
|
64
|
+
|
|
65
|
+
@dataclass
|
|
66
|
+
class ExecutionRequest:
|
|
67
|
+
"""Service execution request"""
|
|
68
|
+
service_id: str
|
|
69
|
+
buyer_wallet_id: str
|
|
70
|
+
input: Dict[str, Any]
|
|
71
|
+
|
|
72
|
+
|
|
73
|
+
@dataclass
|
|
74
|
+
class ExecutionResult:
|
|
75
|
+
"""Service execution result"""
|
|
76
|
+
payment_id: str
|
|
77
|
+
service_id: str
|
|
78
|
+
output: Dict[str, Any]
|
|
79
|
+
execution_time_ms: int
|
|
80
|
+
status: ExecutionStatus
|
|
81
|
+
receipt: Optional['ExecutionReceipt'] = None
|
|
82
|
+
payment: Optional[Payment] = None
|
|
83
|
+
|
|
84
|
+
|
|
85
|
+
@dataclass
|
|
86
|
+
class ExecutionReceipt:
|
|
87
|
+
"""Cryptographic execution receipt"""
|
|
88
|
+
id: str
|
|
89
|
+
payment_id: str
|
|
90
|
+
service_id: str
|
|
91
|
+
input_hash: str
|
|
92
|
+
output_hash: str
|
|
93
|
+
timestamp: int
|
|
94
|
+
execution_time_ms: int
|
|
95
|
+
provider_signature: str
|
|
96
|
+
platform_signature: str
|
|
97
|
+
receipt_hash: str
|
|
98
|
+
blockchain_tx_id: Optional[str] = None
|
|
99
|
+
blockchain_anchored_at: Optional[str] = None
|
|
100
|
+
|
|
101
|
+
|
|
102
|
+
@dataclass
|
|
103
|
+
class ReputationScore:
|
|
104
|
+
"""Agent reputation metrics"""
|
|
105
|
+
agent_id: str
|
|
106
|
+
total_jobs: int
|
|
107
|
+
success_rate: float # 0-1
|
|
108
|
+
avg_response_time_ms: float
|
|
109
|
+
total_earned: int # satoshis
|
|
110
|
+
total_spent: int # satoshis
|
|
111
|
+
rating: float # 1-5
|
|
112
|
+
|
|
113
|
+
|
|
114
|
+
@dataclass
|
|
115
|
+
class Dispute:
|
|
116
|
+
"""Dispute record"""
|
|
117
|
+
id: str
|
|
118
|
+
payment_id: str
|
|
119
|
+
buyer_wallet_id: str
|
|
120
|
+
provider_wallet_id: str
|
|
121
|
+
reason: str
|
|
122
|
+
status: DisputeStatus
|
|
123
|
+
created_at: str
|
|
124
|
+
evidence: Optional[str] = None
|
|
125
|
+
resolution: Optional[DisputeResolution] = None
|
|
126
|
+
split_percent: Optional[float] = None
|
|
127
|
+
resolved_at: Optional[str] = None
|
|
128
|
+
|
|
129
|
+
|
|
130
|
+
@dataclass
|
|
131
|
+
class Webhook:
|
|
132
|
+
"""Webhook registration"""
|
|
133
|
+
id: str
|
|
134
|
+
url: str
|
|
135
|
+
events: list[str]
|
|
136
|
+
active: bool
|
|
137
|
+
created_at: str
|
|
138
|
+
secret: Optional[str] = None
|
|
139
|
+
|
|
140
|
+
|
|
141
|
+
@dataclass
|
|
142
|
+
class ServiceQuery:
|
|
143
|
+
"""Service search query"""
|
|
144
|
+
category: Optional[str] = None
|
|
145
|
+
keyword: Optional[str] = None
|
|
146
|
+
max_price: Optional[int] = None
|
|
147
|
+
min_rating: Optional[float] = None
|
|
148
|
+
limit: Optional[int] = 20
|
|
149
|
+
offset: Optional[int] = 0
|
|
150
|
+
|
|
151
|
+
|
|
152
|
+
# Constants
|
|
153
|
+
PLATFORM_FEE_RATE = 0.02 # 2%
|
|
154
|
+
MIN_PRICE_SATOSHIS = 1
|
|
@@ -0,0 +1,113 @@
|
|
|
1
|
+
"""Wallet operations for AgentPay SDK"""
|
|
2
|
+
|
|
3
|
+
from typing import Optional
|
|
4
|
+
import requests
|
|
5
|
+
from .types import AgentWallet
|
|
6
|
+
from .exceptions import WalletError, APIError
|
|
7
|
+
|
|
8
|
+
|
|
9
|
+
class WalletOperations:
|
|
10
|
+
"""Handles wallet-related operations"""
|
|
11
|
+
|
|
12
|
+
def __init__(self, base_url: str, api_key: Optional[str] = None):
|
|
13
|
+
self.base_url = base_url
|
|
14
|
+
self.api_key = api_key
|
|
15
|
+
|
|
16
|
+
def _get_headers(self) -> dict:
|
|
17
|
+
"""Get request headers with API key if available"""
|
|
18
|
+
headers = {"Content-Type": "application/json"}
|
|
19
|
+
if self.api_key:
|
|
20
|
+
headers["Authorization"] = f"Bearer {self.api_key}"
|
|
21
|
+
return headers
|
|
22
|
+
|
|
23
|
+
def create_wallet(self) -> AgentWallet:
|
|
24
|
+
"""
|
|
25
|
+
Create a new agent wallet
|
|
26
|
+
|
|
27
|
+
Returns:
|
|
28
|
+
AgentWallet: The created wallet
|
|
29
|
+
|
|
30
|
+
Raises:
|
|
31
|
+
WalletError: If wallet creation fails
|
|
32
|
+
"""
|
|
33
|
+
try:
|
|
34
|
+
response = requests.post(
|
|
35
|
+
f"{self.base_url}/api/wallets",
|
|
36
|
+
headers=self._get_headers()
|
|
37
|
+
)
|
|
38
|
+
response.raise_for_status()
|
|
39
|
+
data = response.json()
|
|
40
|
+
|
|
41
|
+
wallet_data = data.get("wallet")
|
|
42
|
+
if not wallet_data:
|
|
43
|
+
raise WalletError("Invalid response: missing wallet data")
|
|
44
|
+
|
|
45
|
+
return AgentWallet(
|
|
46
|
+
id=wallet_data["id"],
|
|
47
|
+
public_key=wallet_data["publicKey"],
|
|
48
|
+
address=wallet_data["address"],
|
|
49
|
+
created_at=wallet_data["createdAt"],
|
|
50
|
+
balance=wallet_data.get("balance"),
|
|
51
|
+
balance_mnee=wallet_data.get("balanceMnee")
|
|
52
|
+
)
|
|
53
|
+
except requests.RequestException as e:
|
|
54
|
+
raise WalletError(f"Failed to create wallet: {str(e)}") from e
|
|
55
|
+
|
|
56
|
+
def get_wallet(self, wallet_id: str) -> AgentWallet:
|
|
57
|
+
"""
|
|
58
|
+
Get wallet by ID
|
|
59
|
+
|
|
60
|
+
Args:
|
|
61
|
+
wallet_id: Wallet ID
|
|
62
|
+
|
|
63
|
+
Returns:
|
|
64
|
+
AgentWallet: The wallet
|
|
65
|
+
|
|
66
|
+
Raises:
|
|
67
|
+
WalletError: If wallet not found or request fails
|
|
68
|
+
"""
|
|
69
|
+
try:
|
|
70
|
+
response = requests.get(
|
|
71
|
+
f"{self.base_url}/api/wallets/{wallet_id}",
|
|
72
|
+
headers=self._get_headers()
|
|
73
|
+
)
|
|
74
|
+
response.raise_for_status()
|
|
75
|
+
data = response.json()
|
|
76
|
+
|
|
77
|
+
wallet_data = data.get("wallet")
|
|
78
|
+
if not wallet_data:
|
|
79
|
+
raise WalletError(f"Wallet {wallet_id} not found")
|
|
80
|
+
|
|
81
|
+
return AgentWallet(
|
|
82
|
+
id=wallet_data["id"],
|
|
83
|
+
public_key=wallet_data["publicKey"],
|
|
84
|
+
address=wallet_data["address"],
|
|
85
|
+
created_at=wallet_data["createdAt"],
|
|
86
|
+
balance=wallet_data.get("balance"),
|
|
87
|
+
balance_mnee=wallet_data.get("balanceMnee")
|
|
88
|
+
)
|
|
89
|
+
except requests.RequestException as e:
|
|
90
|
+
raise WalletError(f"Failed to get wallet: {str(e)}") from e
|
|
91
|
+
|
|
92
|
+
def get_balance(self, wallet_id: str, currency: str = "BSV") -> int:
|
|
93
|
+
"""
|
|
94
|
+
Get wallet balance for a specific currency
|
|
95
|
+
|
|
96
|
+
Args:
|
|
97
|
+
wallet_id: Wallet ID
|
|
98
|
+
currency: "BSV" or "MNEE"
|
|
99
|
+
|
|
100
|
+
Returns:
|
|
101
|
+
int: Balance in satoshis (BSV) or cents (MNEE)
|
|
102
|
+
|
|
103
|
+
Raises:
|
|
104
|
+
WalletError: If balance cannot be retrieved
|
|
105
|
+
"""
|
|
106
|
+
wallet = self.get_wallet(wallet_id)
|
|
107
|
+
|
|
108
|
+
if currency.upper() == "BSV":
|
|
109
|
+
return wallet.balance or 0
|
|
110
|
+
elif currency.upper() == "MNEE":
|
|
111
|
+
return wallet.balance_mnee or 0
|
|
112
|
+
else:
|
|
113
|
+
raise WalletError(f"Invalid currency: {currency}")
|
|
@@ -0,0 +1,195 @@
|
|
|
1
|
+
"""Webhook management for AgentPay SDK"""
|
|
2
|
+
|
|
3
|
+
from typing import Optional, List
|
|
4
|
+
import requests
|
|
5
|
+
from .types import Webhook
|
|
6
|
+
from .exceptions import WebhookError
|
|
7
|
+
|
|
8
|
+
|
|
9
|
+
class WebhookOperations:
|
|
10
|
+
"""Handles webhook registration and management"""
|
|
11
|
+
|
|
12
|
+
def __init__(self, base_url: str, api_key: Optional[str] = None):
|
|
13
|
+
self.base_url = base_url
|
|
14
|
+
self.api_key = api_key
|
|
15
|
+
|
|
16
|
+
def _get_headers(self) -> dict:
|
|
17
|
+
"""Get request headers with API key if available"""
|
|
18
|
+
headers = {"Content-Type": "application/json"}
|
|
19
|
+
if self.api_key:
|
|
20
|
+
headers["Authorization"] = f"Bearer {self.api_key}"
|
|
21
|
+
return headers
|
|
22
|
+
|
|
23
|
+
def register_webhook(
|
|
24
|
+
self,
|
|
25
|
+
url: str,
|
|
26
|
+
events: List[str]
|
|
27
|
+
) -> Webhook:
|
|
28
|
+
"""
|
|
29
|
+
Register a new webhook
|
|
30
|
+
|
|
31
|
+
Args:
|
|
32
|
+
url: Webhook endpoint URL
|
|
33
|
+
events: List of events to subscribe to
|
|
34
|
+
(e.g., ["payment.completed", "payment.failed", "dispute.opened"])
|
|
35
|
+
|
|
36
|
+
Returns:
|
|
37
|
+
Webhook: The registered webhook
|
|
38
|
+
|
|
39
|
+
Raises:
|
|
40
|
+
WebhookError: If registration fails
|
|
41
|
+
"""
|
|
42
|
+
payload = {
|
|
43
|
+
"url": url,
|
|
44
|
+
"events": events
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
try:
|
|
48
|
+
response = requests.post(
|
|
49
|
+
f"{self.base_url}/api/webhooks",
|
|
50
|
+
json=payload,
|
|
51
|
+
headers=self._get_headers()
|
|
52
|
+
)
|
|
53
|
+
response.raise_for_status()
|
|
54
|
+
data = response.json()
|
|
55
|
+
|
|
56
|
+
webhook_data = data.get("webhook")
|
|
57
|
+
if not webhook_data:
|
|
58
|
+
raise WebhookError("Invalid response: missing webhook data")
|
|
59
|
+
|
|
60
|
+
return self._parse_webhook(webhook_data)
|
|
61
|
+
except requests.RequestException as e:
|
|
62
|
+
raise WebhookError(f"Failed to register webhook: {str(e)}") from e
|
|
63
|
+
|
|
64
|
+
def get_webhook(self, webhook_id: str) -> Webhook:
|
|
65
|
+
"""
|
|
66
|
+
Get webhook by ID
|
|
67
|
+
|
|
68
|
+
Args:
|
|
69
|
+
webhook_id: Webhook ID
|
|
70
|
+
|
|
71
|
+
Returns:
|
|
72
|
+
Webhook: The webhook
|
|
73
|
+
|
|
74
|
+
Raises:
|
|
75
|
+
WebhookError: If webhook not found or request fails
|
|
76
|
+
"""
|
|
77
|
+
try:
|
|
78
|
+
response = requests.get(
|
|
79
|
+
f"{self.base_url}/api/webhooks/{webhook_id}",
|
|
80
|
+
headers=self._get_headers()
|
|
81
|
+
)
|
|
82
|
+
response.raise_for_status()
|
|
83
|
+
data = response.json()
|
|
84
|
+
|
|
85
|
+
webhook_data = data.get("webhook")
|
|
86
|
+
if not webhook_data:
|
|
87
|
+
raise WebhookError(f"Webhook {webhook_id} not found")
|
|
88
|
+
|
|
89
|
+
return self._parse_webhook(webhook_data)
|
|
90
|
+
except requests.RequestException as e:
|
|
91
|
+
raise WebhookError(f"Failed to get webhook: {str(e)}") from e
|
|
92
|
+
|
|
93
|
+
def list_webhooks(self) -> List[Webhook]:
|
|
94
|
+
"""
|
|
95
|
+
List all registered webhooks
|
|
96
|
+
|
|
97
|
+
Returns:
|
|
98
|
+
List[Webhook]: List of webhooks
|
|
99
|
+
|
|
100
|
+
Raises:
|
|
101
|
+
WebhookError: If request fails
|
|
102
|
+
"""
|
|
103
|
+
try:
|
|
104
|
+
response = requests.get(
|
|
105
|
+
f"{self.base_url}/api/webhooks",
|
|
106
|
+
headers=self._get_headers()
|
|
107
|
+
)
|
|
108
|
+
response.raise_for_status()
|
|
109
|
+
data = response.json()
|
|
110
|
+
|
|
111
|
+
webhooks_data = data.get("webhooks", [])
|
|
112
|
+
return [self._parse_webhook(w) for w in webhooks_data]
|
|
113
|
+
except requests.RequestException as e:
|
|
114
|
+
raise WebhookError(f"Failed to list webhooks: {str(e)}") from e
|
|
115
|
+
|
|
116
|
+
def delete_webhook(self, webhook_id: str) -> bool:
|
|
117
|
+
"""
|
|
118
|
+
Delete a webhook
|
|
119
|
+
|
|
120
|
+
Args:
|
|
121
|
+
webhook_id: Webhook ID
|
|
122
|
+
|
|
123
|
+
Returns:
|
|
124
|
+
bool: True if deleted successfully
|
|
125
|
+
|
|
126
|
+
Raises:
|
|
127
|
+
WebhookError: If deletion fails
|
|
128
|
+
"""
|
|
129
|
+
try:
|
|
130
|
+
response = requests.delete(
|
|
131
|
+
f"{self.base_url}/api/webhooks/{webhook_id}",
|
|
132
|
+
headers=self._get_headers()
|
|
133
|
+
)
|
|
134
|
+
response.raise_for_status()
|
|
135
|
+
return True
|
|
136
|
+
except requests.RequestException as e:
|
|
137
|
+
raise WebhookError(f"Failed to delete webhook: {str(e)}") from e
|
|
138
|
+
|
|
139
|
+
def update_webhook(
|
|
140
|
+
self,
|
|
141
|
+
webhook_id: str,
|
|
142
|
+
url: Optional[str] = None,
|
|
143
|
+
events: Optional[List[str]] = None,
|
|
144
|
+
active: Optional[bool] = None
|
|
145
|
+
) -> Webhook:
|
|
146
|
+
"""
|
|
147
|
+
Update a webhook
|
|
148
|
+
|
|
149
|
+
Args:
|
|
150
|
+
webhook_id: Webhook ID
|
|
151
|
+
url: New URL (optional)
|
|
152
|
+
events: New events list (optional)
|
|
153
|
+
active: Active status (optional)
|
|
154
|
+
|
|
155
|
+
Returns:
|
|
156
|
+
Webhook: Updated webhook
|
|
157
|
+
|
|
158
|
+
Raises:
|
|
159
|
+
WebhookError: If update fails
|
|
160
|
+
"""
|
|
161
|
+
payload = {}
|
|
162
|
+
if url is not None:
|
|
163
|
+
payload["url"] = url
|
|
164
|
+
if events is not None:
|
|
165
|
+
payload["events"] = events
|
|
166
|
+
if active is not None:
|
|
167
|
+
payload["active"] = active
|
|
168
|
+
|
|
169
|
+
try:
|
|
170
|
+
response = requests.patch(
|
|
171
|
+
f"{self.base_url}/api/webhooks/{webhook_id}",
|
|
172
|
+
json=payload,
|
|
173
|
+
headers=self._get_headers()
|
|
174
|
+
)
|
|
175
|
+
response.raise_for_status()
|
|
176
|
+
data = response.json()
|
|
177
|
+
|
|
178
|
+
webhook_data = data.get("webhook")
|
|
179
|
+
if not webhook_data:
|
|
180
|
+
raise WebhookError("Invalid response: missing webhook data")
|
|
181
|
+
|
|
182
|
+
return self._parse_webhook(webhook_data)
|
|
183
|
+
except requests.RequestException as e:
|
|
184
|
+
raise WebhookError(f"Failed to update webhook: {str(e)}") from e
|
|
185
|
+
|
|
186
|
+
def _parse_webhook(self, data: dict) -> Webhook:
|
|
187
|
+
"""Parse webhook data from API response"""
|
|
188
|
+
return Webhook(
|
|
189
|
+
id=data["id"],
|
|
190
|
+
url=data["url"],
|
|
191
|
+
events=data["events"],
|
|
192
|
+
active=data["active"],
|
|
193
|
+
created_at=data["createdAt"],
|
|
194
|
+
secret=data.get("secret")
|
|
195
|
+
)
|