dominusnode 1.0.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.
@@ -0,0 +1,151 @@
1
+ """Dominus Node Python SDK.
2
+
3
+ Official Python SDK for the Dominus Node rotating proxy-as-a-service platform.
4
+
5
+ Provides both synchronous and asynchronous clients::
6
+
7
+ # Sync
8
+ from dominusnode import DominusNodeClient
9
+
10
+ with DominusNodeClient(base_url="http://localhost:3000") as client:
11
+ client.connect_with_credentials("user@example.com", "s3cret!Pass")
12
+ print(client.wallet.get_balance())
13
+
14
+ # Async
15
+ from dominusnode import AsyncDominusNodeClient
16
+
17
+ async with AsyncDominusNodeClient(base_url="http://localhost:3000") as client:
18
+ await client.connect_with_credentials("user@example.com", "s3cret!Pass")
19
+ print(await client.wallet.get_balance())
20
+ """
21
+
22
+ from .client import AsyncDominusNodeClient, DominusNodeClient
23
+ from .constants import SDK_VERSION
24
+ from .errors import (
25
+ AuthenticationError,
26
+ AuthorizationError,
27
+ ConflictError,
28
+ DominusNodeError,
29
+ InsufficientBalanceError,
30
+ NetworkError,
31
+ NotFoundError,
32
+ ProxyError,
33
+ RateLimitError,
34
+ ServerError,
35
+ ValidationError,
36
+ )
37
+ from .x402 import X402Info, X402Facilitator, X402Pricing
38
+ from .wallet_auth import WalletChallenge, WalletVerifyResult, WalletLinkResult
39
+ from .types import (
40
+ ActiveSession,
41
+ AdminUser,
42
+ AdminUserDetail,
43
+ AdminUsersResponse,
44
+ ApiKey,
45
+ CreatedApiKey,
46
+ CryptoInvoice,
47
+ DailyRevenue,
48
+ DailyUsage,
49
+ GeoTargeting,
50
+ LoginResult,
51
+ MfaSetup,
52
+ MfaStatus,
53
+ Pagination,
54
+ Plan,
55
+ ProxyConfig,
56
+ ProxyEndpointConfig,
57
+ ProxyEndpoints,
58
+ ProxyHealth,
59
+ ProxyStatus,
60
+ ProxyUrlOptions,
61
+ ProviderStat,
62
+ RevenueStats,
63
+ StripeCheckout,
64
+ SlotsInfo,
65
+ SystemStats,
66
+ TopHost,
67
+ UsagePagination,
68
+ UsagePeriod,
69
+ UsageRecord,
70
+ UsageResponse,
71
+ UsageSummary,
72
+ User,
73
+ UserPlanInfo,
74
+ UserPlanUsage,
75
+ Wallet,
76
+ WaitlistCount,
77
+ WaitlistJoinResult,
78
+ WalletForecast,
79
+ WalletTransaction,
80
+ )
81
+
82
+ __version__ = SDK_VERSION
83
+
84
+ __all__ = [
85
+ # Clients
86
+ "DominusNodeClient",
87
+ "AsyncDominusNodeClient",
88
+ # Errors
89
+ "DominusNodeError",
90
+ "AuthenticationError",
91
+ "AuthorizationError",
92
+ "RateLimitError",
93
+ "InsufficientBalanceError",
94
+ "ValidationError",
95
+ "NotFoundError",
96
+ "ConflictError",
97
+ "ServerError",
98
+ "NetworkError",
99
+ "ProxyError",
100
+ # Types
101
+ "User",
102
+ "LoginResult",
103
+ "MfaStatus",
104
+ "MfaSetup",
105
+ "ApiKey",
106
+ "CreatedApiKey",
107
+ "Wallet",
108
+ "WalletTransaction",
109
+ "WalletForecast",
110
+ "StripeCheckout",
111
+ "CryptoInvoice",
112
+ "UsageSummary",
113
+ "UsageRecord",
114
+ "UsagePagination",
115
+ "UsagePeriod",
116
+ "UsageResponse",
117
+ "DailyUsage",
118
+ "TopHost",
119
+ "Plan",
120
+ "UserPlanUsage",
121
+ "UserPlanInfo",
122
+ "ActiveSession",
123
+ "ProxyUrlOptions",
124
+ "ProxyHealth",
125
+ "ProviderStat",
126
+ "ProxyEndpoints",
127
+ "ProxyStatus",
128
+ "GeoTargeting",
129
+ "ProxyEndpointConfig",
130
+ "ProxyConfig",
131
+ "AdminUser",
132
+ "AdminUserDetail",
133
+ "Pagination",
134
+ "AdminUsersResponse",
135
+ "RevenueStats",
136
+ "DailyRevenue",
137
+ "SlotsInfo",
138
+ "WaitlistJoinResult",
139
+ "WaitlistCount",
140
+ "SystemStats",
141
+ # x402
142
+ "X402Info",
143
+ "X402Facilitator",
144
+ "X402Pricing",
145
+ # Wallet Auth
146
+ "WalletChallenge",
147
+ "WalletVerifyResult",
148
+ "WalletLinkResult",
149
+ # Constants
150
+ "SDK_VERSION",
151
+ ]
dominusnode/admin.py ADDED
@@ -0,0 +1,252 @@
1
+ """Admin resource -- user management, revenue analytics, and system stats.
2
+
3
+ All admin endpoints require the authenticated user to have admin privileges.
4
+ """
5
+
6
+ from __future__ import annotations
7
+
8
+ from typing import List, Optional
9
+ from urllib.parse import quote
10
+
11
+ from .http_client import AsyncHttpClient, SyncHttpClient
12
+ from .types import (
13
+ AdminUser,
14
+ AdminUserDetail,
15
+ AdminUsersResponse,
16
+ DailyRevenue,
17
+ Pagination,
18
+ RevenueStats,
19
+ SystemStats,
20
+ UsagePeriod,
21
+ )
22
+
23
+
24
+ def _parse_admin_user(u: dict) -> AdminUser:
25
+ return AdminUser(
26
+ id=u["id"],
27
+ email=u["email"],
28
+ status=u["status"],
29
+ plan_id=u.get("planId", "payg"),
30
+ balance_cents=u.get("balanceCents", 0),
31
+ created_at=str(u.get("createdAt", "")),
32
+ is_admin=u.get("isAdmin", False),
33
+ )
34
+
35
+
36
+ def _parse_admin_user_detail(u: dict) -> AdminUserDetail:
37
+ return AdminUserDetail(
38
+ id=u["id"],
39
+ email=u["email"],
40
+ status=u["status"],
41
+ plan_id=u.get("planId", "payg"),
42
+ balance_cents=u.get("balanceCents", 0),
43
+ created_at=str(u.get("createdAt", "")),
44
+ is_admin=u.get("isAdmin", False),
45
+ api_key_count=u.get("apiKeyCount", 0),
46
+ total_usage_bytes=u.get("totalUsageBytes", 0),
47
+ total_spent_cents=u.get("totalSpentCents", 0),
48
+ last_active=str(u["lastActive"]) if u.get("lastActive") else None,
49
+ )
50
+
51
+
52
+ def _build_date_params(since: Optional[str], until: Optional[str]) -> dict:
53
+ params: dict = {}
54
+ if since:
55
+ params["since"] = since
56
+ if until:
57
+ params["until"] = until
58
+ return params
59
+
60
+
61
+ # ──────────────────────────────────────────────────────────────────────
62
+ # Sync
63
+ # ──────────────────────────────────────────────────────────────────────
64
+
65
+
66
+ class AdminResource:
67
+ """Synchronous admin operations."""
68
+
69
+ def __init__(self, http: SyncHttpClient) -> None:
70
+ self._http = http
71
+
72
+ # ── User Management ──────────────────────────────────────────────
73
+
74
+ def list_users(self, *, page: int = 1, limit: int = 20) -> AdminUsersResponse:
75
+ """List all users (paginated)."""
76
+ data = self._http.get("/api/admin/users", params={"page": page, "limit": limit})
77
+ users = [_parse_admin_user(u) for u in data.get("users", [])]
78
+ p = data.get("pagination", {})
79
+ pagination = Pagination(
80
+ page=p.get("page", page),
81
+ limit=p.get("limit", limit),
82
+ total=p.get("total", 0),
83
+ total_pages=p.get("totalPages", 0),
84
+ )
85
+ return AdminUsersResponse(users=users, pagination=pagination)
86
+
87
+ def get_user(self, user_id: str) -> AdminUserDetail:
88
+ """Get detailed information about a specific user."""
89
+ data = self._http.get(f"/api/admin/users/{quote(user_id, safe='')}")
90
+ return _parse_admin_user_detail(data["user"])
91
+
92
+ def suspend_user(self, user_id: str) -> None:
93
+ """Suspend a user account."""
94
+ self._http.put(f"/api/admin/users/{quote(user_id, safe='')}/suspend")
95
+
96
+ def activate_user(self, user_id: str) -> None:
97
+ """Reactivate a suspended user account."""
98
+ self._http.put(f"/api/admin/users/{quote(user_id, safe='')}/activate")
99
+
100
+ def delete_user(self, user_id: str) -> None:
101
+ """Soft-delete a user account."""
102
+ self._http.delete(f"/api/admin/users/{quote(user_id, safe='')}")
103
+
104
+ # ── Revenue Analytics ────────────────────────────────────────────
105
+
106
+ def get_revenue(
107
+ self,
108
+ *,
109
+ since: Optional[str] = None,
110
+ until: Optional[str] = None,
111
+ ) -> RevenueStats:
112
+ """Get revenue statistics for the given period."""
113
+ params = _build_date_params(since, until)
114
+ data = self._http.get("/api/admin/revenue", params=params)
115
+ period_data = data.get("period", {})
116
+ return RevenueStats(
117
+ total_revenue_cents=data.get("totalRevenueCents", 0),
118
+ total_revenue_usd=data.get("totalRevenueUsd", 0),
119
+ total_transactions=data.get("totalTransactions", 0),
120
+ avg_transaction_cents=data.get("avgTransactionCents", 0),
121
+ avg_transaction_usd=data.get("avgTransactionUsd", 0),
122
+ top_up_count=data.get("topUpCount", 0),
123
+ unique_paying_users=data.get("uniquePayingUsers", 0),
124
+ period=UsagePeriod(
125
+ since=period_data.get("since", ""),
126
+ until=period_data.get("until", ""),
127
+ ),
128
+ )
129
+
130
+ def get_daily_revenue(
131
+ self,
132
+ *,
133
+ since: Optional[str] = None,
134
+ until: Optional[str] = None,
135
+ ) -> List[DailyRevenue]:
136
+ """Get daily revenue breakdown."""
137
+ params = _build_date_params(since, until)
138
+ data = self._http.get("/api/admin/revenue/daily", params=params)
139
+ return [
140
+ DailyRevenue(
141
+ date=d["date"],
142
+ revenue_cents=d.get("revenueCents", 0),
143
+ revenue_usd=d.get("revenueUsd", 0),
144
+ transaction_count=d.get("transactionCount", 0),
145
+ )
146
+ for d in data.get("days", [])
147
+ ]
148
+
149
+ # ── System Stats ─────────────────────────────────────────────────
150
+
151
+ def get_stats(self) -> SystemStats:
152
+ """Get system-wide statistics."""
153
+ data = self._http.get("/api/admin/stats")
154
+ return SystemStats(
155
+ total_users=data.get("totalUsers", 0),
156
+ active_users=data.get("activeUsers", 0),
157
+ suspended_users=data.get("suspendedUsers", 0),
158
+ total_api_keys=data.get("totalApiKeys", 0),
159
+ active_api_keys=data.get("activeApiKeys", 0),
160
+ total_sessions=data.get("totalSessions", 0),
161
+ active_sessions=data.get("activeSessions", 0),
162
+ )
163
+
164
+
165
+ # ──────────────────────────────────────────────────────────────────────
166
+ # Async
167
+ # ──────────────────────────────────────────────────────────────────────
168
+
169
+
170
+ class AsyncAdminResource:
171
+ """Asynchronous admin operations."""
172
+
173
+ def __init__(self, http: AsyncHttpClient) -> None:
174
+ self._http = http
175
+
176
+ async def list_users(self, *, page: int = 1, limit: int = 20) -> AdminUsersResponse:
177
+ data = await self._http.get("/api/admin/users", params={"page": page, "limit": limit})
178
+ users = [_parse_admin_user(u) for u in data.get("users", [])]
179
+ p = data.get("pagination", {})
180
+ pagination = Pagination(
181
+ page=p.get("page", page),
182
+ limit=p.get("limit", limit),
183
+ total=p.get("total", 0),
184
+ total_pages=p.get("totalPages", 0),
185
+ )
186
+ return AdminUsersResponse(users=users, pagination=pagination)
187
+
188
+ async def get_user(self, user_id: str) -> AdminUserDetail:
189
+ data = await self._http.get(f"/api/admin/users/{quote(user_id, safe='')}")
190
+ return _parse_admin_user_detail(data["user"])
191
+
192
+ async def suspend_user(self, user_id: str) -> None:
193
+ await self._http.put(f"/api/admin/users/{quote(user_id, safe='')}/suspend")
194
+
195
+ async def activate_user(self, user_id: str) -> None:
196
+ await self._http.put(f"/api/admin/users/{quote(user_id, safe='')}/activate")
197
+
198
+ async def delete_user(self, user_id: str) -> None:
199
+ await self._http.delete(f"/api/admin/users/{quote(user_id, safe='')}")
200
+
201
+ async def get_revenue(
202
+ self,
203
+ *,
204
+ since: Optional[str] = None,
205
+ until: Optional[str] = None,
206
+ ) -> RevenueStats:
207
+ params = _build_date_params(since, until)
208
+ data = await self._http.get("/api/admin/revenue", params=params)
209
+ period_data = data.get("period", {})
210
+ return RevenueStats(
211
+ total_revenue_cents=data.get("totalRevenueCents", 0),
212
+ total_revenue_usd=data.get("totalRevenueUsd", 0),
213
+ total_transactions=data.get("totalTransactions", 0),
214
+ avg_transaction_cents=data.get("avgTransactionCents", 0),
215
+ avg_transaction_usd=data.get("avgTransactionUsd", 0),
216
+ top_up_count=data.get("topUpCount", 0),
217
+ unique_paying_users=data.get("uniquePayingUsers", 0),
218
+ period=UsagePeriod(
219
+ since=period_data.get("since", ""),
220
+ until=period_data.get("until", ""),
221
+ ),
222
+ )
223
+
224
+ async def get_daily_revenue(
225
+ self,
226
+ *,
227
+ since: Optional[str] = None,
228
+ until: Optional[str] = None,
229
+ ) -> List[DailyRevenue]:
230
+ params = _build_date_params(since, until)
231
+ data = await self._http.get("/api/admin/revenue/daily", params=params)
232
+ return [
233
+ DailyRevenue(
234
+ date=d["date"],
235
+ revenue_cents=d.get("revenueCents", 0),
236
+ revenue_usd=d.get("revenueUsd", 0),
237
+ transaction_count=d.get("transactionCount", 0),
238
+ )
239
+ for d in data.get("days", [])
240
+ ]
241
+
242
+ async def get_stats(self) -> SystemStats:
243
+ data = await self._http.get("/api/admin/stats")
244
+ return SystemStats(
245
+ total_users=data.get("totalUsers", 0),
246
+ active_users=data.get("activeUsers", 0),
247
+ suspended_users=data.get("suspendedUsers", 0),
248
+ total_api_keys=data.get("totalApiKeys", 0),
249
+ active_api_keys=data.get("activeApiKeys", 0),
250
+ total_sessions=data.get("totalSessions", 0),
251
+ active_sessions=data.get("activeSessions", 0),
252
+ )
@@ -0,0 +1,240 @@
1
+ """Agentic wallet resource -- server-side custodial sub-wallets with spending limits."""
2
+
3
+ from __future__ import annotations
4
+
5
+ from typing import List, Optional
6
+ from urllib.parse import quote
7
+
8
+ from .http_client import AsyncHttpClient, SyncHttpClient
9
+ from .types import AgenticWallet, AgenticWalletTransaction
10
+ from .wallet import _validate_amount_cents
11
+
12
+
13
+ def _parse_agentic_wallet(data: dict) -> AgenticWallet:
14
+ """Parse an agentic wallet response dict into an AgenticWallet dataclass."""
15
+ return AgenticWallet(
16
+ id=data["id"],
17
+ label=data["label"],
18
+ balance_cents=data["balanceCents"],
19
+ spending_limit_cents=data["spendingLimitCents"],
20
+ status=data["status"],
21
+ created_at=data.get("createdAt", ""),
22
+ daily_limit_cents=data.get("dailyLimitCents"),
23
+ allowed_domains=data.get("allowedDomains"),
24
+ )
25
+
26
+
27
+ class AgenticWalletResource:
28
+ """Synchronous agentic wallet operations."""
29
+
30
+ def __init__(self, http: SyncHttpClient) -> None:
31
+ self._http = http
32
+
33
+ def create(
34
+ self,
35
+ label: str,
36
+ spending_limit_cents: int = 10000,
37
+ daily_limit_cents: Optional[int] = None,
38
+ allowed_domains: Optional[List[str]] = None,
39
+ ) -> AgenticWallet:
40
+ """Create a new agentic wallet."""
41
+ _validate_amount_cents(spending_limit_cents, "spending_limit_cents")
42
+ body: dict = {
43
+ "label": label,
44
+ "spendingLimitCents": spending_limit_cents,
45
+ }
46
+ if daily_limit_cents is not None:
47
+ _validate_amount_cents(daily_limit_cents, "daily_limit_cents")
48
+ body["dailyLimitCents"] = daily_limit_cents
49
+ if allowed_domains is not None:
50
+ body["allowedDomains"] = allowed_domains
51
+ data = self._http.post("/api/agent-wallet", json=body)
52
+ return _parse_agentic_wallet(data)
53
+
54
+ def list(self) -> List[AgenticWallet]:
55
+ """List all agentic wallets."""
56
+ data = self._http.get("/api/agent-wallet")
57
+ return [_parse_agentic_wallet(w) for w in data["wallets"]]
58
+
59
+ def get(self, wallet_id: str) -> AgenticWallet:
60
+ """Get a single agentic wallet."""
61
+ data = self._http.get(f"/api/agent-wallet/{quote(wallet_id, safe='')}")
62
+ return _parse_agentic_wallet(data)
63
+
64
+ def fund(self, wallet_id: str, amount_cents: int) -> AgenticWalletTransaction:
65
+ """Fund an agentic wallet from the main wallet."""
66
+ _validate_amount_cents(amount_cents, "amount_cents")
67
+ data = self._http.post(f"/api/agent-wallet/{quote(wallet_id, safe='')}/fund", json={
68
+ "amountCents": amount_cents,
69
+ })
70
+ tx = data["transaction"]
71
+ return AgenticWalletTransaction(
72
+ id=tx["id"],
73
+ wallet_id=tx["walletId"],
74
+ type=tx["type"],
75
+ amount_cents=tx["amountCents"],
76
+ description=tx["description"],
77
+ session_id=tx.get("sessionId"),
78
+ created_at=tx["createdAt"],
79
+ )
80
+
81
+ def transactions(
82
+ self, wallet_id: str, limit: int = 50, offset: int = 0
83
+ ) -> List[AgenticWalletTransaction]:
84
+ """Get transaction history for an agentic wallet."""
85
+ data = self._http.get(
86
+ f"/api/agent-wallet/{quote(wallet_id, safe='')}/transactions?limit={limit}&offset={offset}"
87
+ )
88
+ return [
89
+ AgenticWalletTransaction(
90
+ id=tx["id"],
91
+ wallet_id=tx["walletId"],
92
+ type=tx["type"],
93
+ amount_cents=tx["amountCents"],
94
+ description=tx["description"],
95
+ session_id=tx.get("sessionId"),
96
+ created_at=tx["createdAt"],
97
+ )
98
+ for tx in data["transactions"]
99
+ ]
100
+
101
+ def freeze(self, wallet_id: str) -> AgenticWallet:
102
+ """Freeze an agentic wallet."""
103
+ data = self._http.post(f"/api/agent-wallet/{quote(wallet_id, safe='')}/freeze", json={})
104
+ return _parse_agentic_wallet(data)
105
+
106
+ def unfreeze(self, wallet_id: str) -> AgenticWallet:
107
+ """Unfreeze an agentic wallet."""
108
+ data = self._http.post(f"/api/agent-wallet/{quote(wallet_id, safe='')}/unfreeze", json={})
109
+ return _parse_agentic_wallet(data)
110
+
111
+ def delete(self, wallet_id: str) -> int:
112
+ """Delete an agentic wallet and refund remaining balance. Returns refunded cents."""
113
+ data = self._http.delete(f"/api/agent-wallet/{quote(wallet_id, safe='')}")
114
+ return data["refundedCents"]
115
+
116
+ def update_wallet_policy(
117
+ self,
118
+ wallet_id: str,
119
+ daily_limit_cents: Optional[int] = None,
120
+ allowed_domains: Optional[List[str]] = None,
121
+ ) -> AgenticWallet:
122
+ """Update the policy on an agentic wallet."""
123
+ body: dict = {}
124
+ if daily_limit_cents is not None:
125
+ _validate_amount_cents(daily_limit_cents, "daily_limit_cents")
126
+ body["dailyLimitCents"] = daily_limit_cents
127
+ if allowed_domains is not None:
128
+ body["allowedDomains"] = allowed_domains
129
+ data = self._http.patch(
130
+ f"/api/agent-wallet/{quote(wallet_id, safe='')}/policy", json=body
131
+ )
132
+ return _parse_agentic_wallet(data)
133
+
134
+
135
+ class AsyncAgenticWalletResource:
136
+ """Asynchronous agentic wallet operations."""
137
+
138
+ def __init__(self, http: AsyncHttpClient) -> None:
139
+ self._http = http
140
+
141
+ async def create(
142
+ self,
143
+ label: str,
144
+ spending_limit_cents: int = 10000,
145
+ daily_limit_cents: Optional[int] = None,
146
+ allowed_domains: Optional[List[str]] = None,
147
+ ) -> AgenticWallet:
148
+ """Create a new agentic wallet."""
149
+ _validate_amount_cents(spending_limit_cents, "spending_limit_cents")
150
+ body: dict = {
151
+ "label": label,
152
+ "spendingLimitCents": spending_limit_cents,
153
+ }
154
+ if daily_limit_cents is not None:
155
+ _validate_amount_cents(daily_limit_cents, "daily_limit_cents")
156
+ body["dailyLimitCents"] = daily_limit_cents
157
+ if allowed_domains is not None:
158
+ body["allowedDomains"] = allowed_domains
159
+ data = await self._http.post("/api/agent-wallet", json=body)
160
+ return _parse_agentic_wallet(data)
161
+
162
+ async def list(self) -> List[AgenticWallet]:
163
+ """List all agentic wallets."""
164
+ data = await self._http.get("/api/agent-wallet")
165
+ return [_parse_agentic_wallet(w) for w in data["wallets"]]
166
+
167
+ async def get(self, wallet_id: str) -> AgenticWallet:
168
+ """Get a single agentic wallet."""
169
+ data = await self._http.get(f"/api/agent-wallet/{quote(wallet_id, safe='')}")
170
+ return _parse_agentic_wallet(data)
171
+
172
+ async def fund(self, wallet_id: str, amount_cents: int) -> AgenticWalletTransaction:
173
+ """Fund an agentic wallet from the main wallet."""
174
+ _validate_amount_cents(amount_cents, "amount_cents")
175
+ data = await self._http.post(f"/api/agent-wallet/{quote(wallet_id, safe='')}/fund", json={
176
+ "amountCents": amount_cents,
177
+ })
178
+ tx = data["transaction"]
179
+ return AgenticWalletTransaction(
180
+ id=tx["id"],
181
+ wallet_id=tx["walletId"],
182
+ type=tx["type"],
183
+ amount_cents=tx["amountCents"],
184
+ description=tx["description"],
185
+ session_id=tx.get("sessionId"),
186
+ created_at=tx["createdAt"],
187
+ )
188
+
189
+ async def transactions(
190
+ self, wallet_id: str, limit: int = 50, offset: int = 0
191
+ ) -> List[AgenticWalletTransaction]:
192
+ """Get transaction history for an agentic wallet."""
193
+ data = await self._http.get(
194
+ f"/api/agent-wallet/{quote(wallet_id, safe='')}/transactions?limit={limit}&offset={offset}"
195
+ )
196
+ return [
197
+ AgenticWalletTransaction(
198
+ id=tx["id"],
199
+ wallet_id=tx["walletId"],
200
+ type=tx["type"],
201
+ amount_cents=tx["amountCents"],
202
+ description=tx["description"],
203
+ session_id=tx.get("sessionId"),
204
+ created_at=tx["createdAt"],
205
+ )
206
+ for tx in data["transactions"]
207
+ ]
208
+
209
+ async def freeze(self, wallet_id: str) -> AgenticWallet:
210
+ """Freeze an agentic wallet."""
211
+ data = await self._http.post(f"/api/agent-wallet/{quote(wallet_id, safe='')}/freeze", json={})
212
+ return _parse_agentic_wallet(data)
213
+
214
+ async def unfreeze(self, wallet_id: str) -> AgenticWallet:
215
+ """Unfreeze an agentic wallet."""
216
+ data = await self._http.post(f"/api/agent-wallet/{quote(wallet_id, safe='')}/unfreeze", json={})
217
+ return _parse_agentic_wallet(data)
218
+
219
+ async def delete(self, wallet_id: str) -> int:
220
+ """Delete an agentic wallet and refund remaining balance. Returns refunded cents."""
221
+ data = await self._http.delete(f"/api/agent-wallet/{quote(wallet_id, safe='')}")
222
+ return data["refundedCents"]
223
+
224
+ async def update_wallet_policy(
225
+ self,
226
+ wallet_id: str,
227
+ daily_limit_cents: Optional[int] = None,
228
+ allowed_domains: Optional[List[str]] = None,
229
+ ) -> AgenticWallet:
230
+ """Update the policy on an agentic wallet."""
231
+ body: dict = {}
232
+ if daily_limit_cents is not None:
233
+ _validate_amount_cents(daily_limit_cents, "daily_limit_cents")
234
+ body["dailyLimitCents"] = daily_limit_cents
235
+ if allowed_domains is not None:
236
+ body["allowedDomains"] = allowed_domains
237
+ data = await self._http.patch(
238
+ f"/api/agent-wallet/{quote(wallet_id, safe='')}/policy", json=body
239
+ )
240
+ return _parse_agentic_wallet(data)