sthrip 0.1.0__tar.gz

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.
sthrip-0.1.0/LICENSE ADDED
@@ -0,0 +1,21 @@
1
+ MIT License
2
+
3
+ Copyright (c) 2026 Sthrip Contributors
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining a copy
6
+ of this software and associated documentation files (the "Software"), to deal
7
+ in the Software without restriction, including without limitation the rights
8
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9
+ copies of the Software, and to permit persons to whom the Software is
10
+ furnished to do so, subject to the following conditions:
11
+
12
+ The above copyright notice and this permission notice shall be included in all
13
+ copies or substantial portions of the Software.
14
+
15
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21
+ SOFTWARE.
sthrip-0.1.0/PKG-INFO ADDED
@@ -0,0 +1,83 @@
1
+ Metadata-Version: 2.4
2
+ Name: sthrip
3
+ Version: 0.1.0
4
+ Summary: Anonymous payments for AI agents
5
+ License: MIT
6
+ Project-URL: Homepage, https://sthrip.dev
7
+ Project-URL: Documentation, https://sthrip-api-production.up.railway.app/docs
8
+ Project-URL: Repository, https://github.com/sthrip/sthrip
9
+ Keywords: payments,ai,agents,monero,anonymous
10
+ Classifier: Development Status :: 4 - Beta
11
+ Classifier: Intended Audience :: Developers
12
+ Classifier: License :: OSI Approved :: MIT License
13
+ Classifier: Programming Language :: Python :: 3
14
+ Requires-Python: >=3.8
15
+ Description-Content-Type: text/markdown
16
+ License-File: LICENSE
17
+ Requires-Dist: requests>=2.25
18
+ Dynamic: license-file
19
+
20
+ # sthrip
21
+
22
+ > Anonymous payments for AI agents.
23
+
24
+ [![PyPI version](https://img.shields.io/pypi/v/sthrip)](https://pypi.org/project/sthrip/)
25
+ [![Python 3.8+](https://img.shields.io/badge/python-3.8%2B-blue)](https://www.python.org/downloads/)
26
+ [![License: MIT](https://img.shields.io/badge/License-MIT-yellow.svg)](https://opensource.org/licenses/MIT)
27
+
28
+ ## Quickstart
29
+
30
+ ```python
31
+ from sthrip import Sthrip
32
+
33
+ s = Sthrip() # auto-registers on first use
34
+ s.pay("agent-name", 0.5) # send 0.5 XMR
35
+ ```
36
+
37
+ ## Install
38
+
39
+ ```
40
+ pip install sthrip
41
+ ```
42
+
43
+ ## How it works
44
+
45
+ Agents register and get a unique XMR deposit address. Deposit Monero to the address, then pay other agents through the hub with a single method call. Payments are routed internally -- instant, private, and charged at 0.1% per transaction. The entire system is built on Monero for maximum privacy. No accounts, no KYC, no tracking.
46
+
47
+ ## API Reference
48
+
49
+ | Method | Description |
50
+ |--------|-------------|
51
+ | `Sthrip()` | Initialize client (auto-registers if no credentials found) |
52
+ | `s.deposit_address()` | Get your XMR deposit address |
53
+ | `s.pay(agent, amount)` | Send payment to another agent |
54
+ | `s.balance()` | Check your current balance |
55
+ | `s.find_agents()` | Discover available agents |
56
+ | `s.me()` | View your agent profile |
57
+ | `s.withdraw(amount, addr)` | Withdraw XMR to an external address |
58
+ | `s.payment_history()` | View transaction history |
59
+
60
+ ## Configuration
61
+
62
+ | Variable | Purpose |
63
+ |----------|---------|
64
+ | `STHRIP_API_KEY` | API key for authentication |
65
+ | `STHRIP_API_URL` | Custom API URL (optional) |
66
+
67
+ Credentials are auto-saved to `~/.sthrip/credentials.json` on first registration.
68
+
69
+ ## Fees
70
+
71
+ - **0.1%** per hub-routed payment
72
+ - No registration fee
73
+ - No deposit fee
74
+
75
+ ## Links
76
+
77
+ - [Landing page](https://sthrip.dev)
78
+ - [API Docs](https://sthrip-api-production.up.railway.app/docs)
79
+ - [PyPI](https://pypi.org/project/sthrip/)
80
+
81
+ ## License
82
+
83
+ [MIT](LICENSE)
sthrip-0.1.0/README.md ADDED
@@ -0,0 +1,64 @@
1
+ # sthrip
2
+
3
+ > Anonymous payments for AI agents.
4
+
5
+ [![PyPI version](https://img.shields.io/pypi/v/sthrip)](https://pypi.org/project/sthrip/)
6
+ [![Python 3.8+](https://img.shields.io/badge/python-3.8%2B-blue)](https://www.python.org/downloads/)
7
+ [![License: MIT](https://img.shields.io/badge/License-MIT-yellow.svg)](https://opensource.org/licenses/MIT)
8
+
9
+ ## Quickstart
10
+
11
+ ```python
12
+ from sthrip import Sthrip
13
+
14
+ s = Sthrip() # auto-registers on first use
15
+ s.pay("agent-name", 0.5) # send 0.5 XMR
16
+ ```
17
+
18
+ ## Install
19
+
20
+ ```
21
+ pip install sthrip
22
+ ```
23
+
24
+ ## How it works
25
+
26
+ Agents register and get a unique XMR deposit address. Deposit Monero to the address, then pay other agents through the hub with a single method call. Payments are routed internally -- instant, private, and charged at 0.1% per transaction. The entire system is built on Monero for maximum privacy. No accounts, no KYC, no tracking.
27
+
28
+ ## API Reference
29
+
30
+ | Method | Description |
31
+ |--------|-------------|
32
+ | `Sthrip()` | Initialize client (auto-registers if no credentials found) |
33
+ | `s.deposit_address()` | Get your XMR deposit address |
34
+ | `s.pay(agent, amount)` | Send payment to another agent |
35
+ | `s.balance()` | Check your current balance |
36
+ | `s.find_agents()` | Discover available agents |
37
+ | `s.me()` | View your agent profile |
38
+ | `s.withdraw(amount, addr)` | Withdraw XMR to an external address |
39
+ | `s.payment_history()` | View transaction history |
40
+
41
+ ## Configuration
42
+
43
+ | Variable | Purpose |
44
+ |----------|---------|
45
+ | `STHRIP_API_KEY` | API key for authentication |
46
+ | `STHRIP_API_URL` | Custom API URL (optional) |
47
+
48
+ Credentials are auto-saved to `~/.sthrip/credentials.json` on first registration.
49
+
50
+ ## Fees
51
+
52
+ - **0.1%** per hub-routed payment
53
+ - No registration fee
54
+ - No deposit fee
55
+
56
+ ## Links
57
+
58
+ - [Landing page](https://sthrip.dev)
59
+ - [API Docs](https://sthrip-api-production.up.railway.app/docs)
60
+ - [PyPI](https://pypi.org/project/sthrip/)
61
+
62
+ ## License
63
+
64
+ [MIT](LICENSE)
@@ -0,0 +1,24 @@
1
+ [project]
2
+ name = "sthrip"
3
+ version = "0.1.0"
4
+ description = "Anonymous payments for AI agents"
5
+ requires-python = ">=3.8"
6
+ dependencies = ["requests>=2.25"]
7
+ license = {text = "MIT"}
8
+ readme = "README.md"
9
+ keywords = ["payments", "ai", "agents", "monero", "anonymous"]
10
+ classifiers = [
11
+ "Development Status :: 4 - Beta",
12
+ "Intended Audience :: Developers",
13
+ "License :: OSI Approved :: MIT License",
14
+ "Programming Language :: Python :: 3",
15
+ ]
16
+
17
+ [project.urls]
18
+ Homepage = "https://sthrip.dev"
19
+ Documentation = "https://sthrip-api-production.up.railway.app/docs"
20
+ Repository = "https://github.com/sthrip/sthrip"
21
+
22
+ [build-system]
23
+ requires = ["setuptools>=61.0"]
24
+ build-backend = "setuptools.build_meta"
sthrip-0.1.0/setup.cfg ADDED
@@ -0,0 +1,4 @@
1
+ [egg_info]
2
+ tag_build =
3
+ tag_date = 0
4
+
@@ -0,0 +1,35 @@
1
+ """Sthrip SDK -- anonymous payments for AI agents.
2
+
3
+ Quick start::
4
+
5
+ from sthrip import Sthrip
6
+
7
+ s = Sthrip() # auto-registers if no key found
8
+ print(s.deposit_address()) # get XMR deposit address
9
+ print(s.balance()) # check balance
10
+ s.pay("other-agent", 0.05) # send payment
11
+ """
12
+
13
+ from .client import Sthrip
14
+ from .exceptions import (
15
+ AgentNotFound,
16
+ AuthError,
17
+ InsufficientBalance,
18
+ NetworkError,
19
+ PaymentError,
20
+ RateLimitError,
21
+ StrhipError,
22
+ )
23
+
24
+ __version__ = "0.1.0"
25
+
26
+ __all__ = [
27
+ "Sthrip",
28
+ "StrhipError",
29
+ "AuthError",
30
+ "PaymentError",
31
+ "InsufficientBalance",
32
+ "AgentNotFound",
33
+ "RateLimitError",
34
+ "NetworkError",
35
+ ]
@@ -0,0 +1,85 @@
1
+ """Credential storage for the Sthrip SDK.
2
+
3
+ Credentials are persisted at ``~/.sthrip/credentials.json`` with file
4
+ permissions restricted to the owning user (0600). The module never
5
+ mutates the dict that is returned by ``load_credentials`` -- callers
6
+ receive a fresh copy each time.
7
+ """
8
+
9
+ import json
10
+ import os
11
+ import stat
12
+ from pathlib import Path
13
+
14
+ # typing import kept compatible with Python 3.8
15
+ from typing import Dict, Optional
16
+
17
+ CREDENTIALS_PATH = Path.home() / ".sthrip" / "credentials.json"
18
+
19
+ # Fields we expect inside the credentials file.
20
+ _REQUIRED_KEYS = ("api_key", "agent_id", "agent_name", "api_url")
21
+
22
+
23
+ def load_credentials(path=None):
24
+ # type: (Optional[Path]) -> Optional[Dict[str, str]]
25
+ """Load credentials from disk.
26
+
27
+ Returns a *new* dict with keys ``api_key``, ``agent_id``,
28
+ ``agent_name``, and ``api_url``, or ``None`` if the file does not
29
+ exist or is malformed.
30
+ """
31
+ target = path or CREDENTIALS_PATH
32
+ if not target.is_file():
33
+ return None
34
+
35
+ try:
36
+ raw = target.read_text(encoding="utf-8")
37
+ data = json.loads(raw)
38
+ except (OSError, json.JSONDecodeError, ValueError):
39
+ return None
40
+
41
+ if not isinstance(data, dict):
42
+ return None
43
+
44
+ # Return only the keys we care about -- never leak unknown fields.
45
+ result = {}
46
+ for key in _REQUIRED_KEYS:
47
+ value = data.get(key)
48
+ if value is None:
49
+ return None
50
+ result[key] = str(value)
51
+
52
+ return result
53
+
54
+
55
+ def save_credentials(api_key, agent_id, agent_name, api_url, path=None):
56
+ # type: (str, str, str, str, Optional[Path]) -> None
57
+ """Persist credentials to disk with restricted permissions.
58
+
59
+ Creates ``~/.sthrip/`` if it does not exist. The file is written
60
+ atomically (write-then-rename would be ideal but pathlib keeps this
61
+ simple) and its mode is set to ``0600`` immediately.
62
+ """
63
+ target = path or CREDENTIALS_PATH
64
+
65
+ target.parent.mkdir(parents=True, exist_ok=True)
66
+
67
+ payload = {
68
+ "api_key": api_key,
69
+ "agent_id": agent_id,
70
+ "agent_name": agent_name,
71
+ "api_url": api_url,
72
+ }
73
+
74
+ target.write_text(
75
+ json.dumps(payload, indent=2) + "\n",
76
+ encoding="utf-8",
77
+ )
78
+
79
+ # Restrict to owner read/write only.
80
+ try:
81
+ os.chmod(str(target), stat.S_IRUSR | stat.S_IWUSR)
82
+ except OSError:
83
+ # On platforms where chmod is not supported (e.g. some Windows
84
+ # builds) we silently continue -- the file was still written.
85
+ pass
@@ -0,0 +1,350 @@
1
+ """Sthrip SDK client -- thin, synchronous wrapper over the Sthrip REST API.
2
+
3
+ Usage::
4
+
5
+ from sthrip import Sthrip
6
+
7
+ s = Sthrip() # auto-registers if no key found
8
+ print(s.balance())
9
+ s.pay("other-agent", 0.05, memo="thanks")
10
+ """
11
+
12
+ import os
13
+ import platform
14
+ import secrets
15
+ import socket
16
+
17
+ import requests
18
+
19
+ from .auth import load_credentials, save_credentials
20
+ from .exceptions import (
21
+ AgentNotFound,
22
+ AuthError,
23
+ InsufficientBalance,
24
+ NetworkError,
25
+ PaymentError,
26
+ RateLimitError,
27
+ StrhipError,
28
+ )
29
+
30
+ _VERSION = "0.1.0"
31
+ _USER_AGENT = "sthrip-sdk/{}".format(_VERSION)
32
+ _DEFAULT_API_URL = "https://sthrip-api-production.up.railway.app"
33
+ _REQUEST_TIMEOUT = 30 # seconds
34
+
35
+
36
+ # ---------------------------------------------------------------------------
37
+ # Helpers
38
+ # ---------------------------------------------------------------------------
39
+
40
+ def _generate_agent_name():
41
+ # type: () -> str
42
+ """Build a human-readable agent name from the hostname + random suffix."""
43
+ try:
44
+ host = socket.gethostname().split(".")[0]
45
+ except Exception:
46
+ host = "agent"
47
+ # Keep only alphanumeric/underscore/hyphen characters.
48
+ safe = "".join(ch for ch in host if ch.isalnum() or ch in ("_", "-"))
49
+ if not safe:
50
+ safe = "agent"
51
+ suffix = secrets.token_hex(4)
52
+ return "{}-{}".format(safe, suffix)
53
+
54
+
55
+ def _resolve_api_url(explicit):
56
+ # type: (str) -> str
57
+ """Return the API base URL without a trailing slash."""
58
+ url = explicit or os.environ.get("STHRIP_API_URL", "") or _DEFAULT_API_URL
59
+ return url.rstrip("/")
60
+
61
+
62
+ def _map_error(status_code, detail):
63
+ # type: (int, str) -> StrhipError
64
+ """Map an HTTP status + detail string to the most specific exception."""
65
+ lower = detail.lower()
66
+
67
+ if status_code == 429:
68
+ return RateLimitError(detail, status_code)
69
+
70
+ if status_code in (401, 403):
71
+ return AuthError(detail, status_code)
72
+
73
+ if status_code == 404 and "agent" in lower:
74
+ return AgentNotFound(detail, status_code)
75
+
76
+ if "insufficient" in lower or "not enough" in lower:
77
+ return InsufficientBalance(detail, status_code)
78
+
79
+ if status_code >= 400:
80
+ return PaymentError(detail, status_code)
81
+
82
+ return StrhipError(detail, status_code)
83
+
84
+
85
+ # ---------------------------------------------------------------------------
86
+ # Client
87
+ # ---------------------------------------------------------------------------
88
+
89
+ class Sthrip(object):
90
+ """Synchronous client for the Sthrip anonymous-payments API.
91
+
92
+ Parameters
93
+ ----------
94
+ api_key : str, optional
95
+ Bearer token. Resolution order: *api_key* argument, then
96
+ ``STHRIP_API_KEY`` env var, then ``~/.sthrip/credentials.json``,
97
+ and finally auto-registration.
98
+ api_url : str, optional
99
+ Base URL of the Sthrip API. Defaults to the env var
100
+ ``STHRIP_API_URL`` or the production endpoint.
101
+ """
102
+
103
+ def __init__(self, api_key=None, api_url=None):
104
+ # type: (str, str) -> None
105
+ self._api_url = _resolve_api_url(api_url)
106
+ self._api_key = self._resolve_api_key(api_key)
107
+ self._session = self._build_session()
108
+
109
+ # -- key resolution -----------------------------------------------------
110
+
111
+ def _resolve_api_key(self, explicit):
112
+ # type: (str) -> str
113
+ """Walk the credential chain and return a usable API key."""
114
+ # 1. Explicit parameter
115
+ if explicit:
116
+ return explicit
117
+
118
+ # 2. Environment variable
119
+ env_key = os.environ.get("STHRIP_API_KEY", "")
120
+ if env_key:
121
+ return env_key
122
+
123
+ # 3. Credential file
124
+ creds = load_credentials()
125
+ if creds is not None:
126
+ return creds["api_key"]
127
+
128
+ # 4. Auto-register
129
+ return self._auto_register()
130
+
131
+ def _auto_register(self):
132
+ # type: () -> str
133
+ """Register a new agent and persist the credentials."""
134
+ agent_name = _generate_agent_name()
135
+ payload = {"agent_name": agent_name, "privacy_level": "medium"}
136
+
137
+ data = self._raw_post(
138
+ "/v2/agents/register",
139
+ json_body=payload,
140
+ authenticated=False,
141
+ )
142
+
143
+ save_credentials(
144
+ api_key=data["api_key"],
145
+ agent_id=data["agent_id"],
146
+ agent_name=data["agent_name"],
147
+ api_url=self._api_url,
148
+ )
149
+
150
+ return data["api_key"]
151
+
152
+ # -- HTTP layer ---------------------------------------------------------
153
+
154
+ def _build_session(self):
155
+ # type: () -> requests.Session
156
+ session = requests.Session()
157
+ session.headers.update({
158
+ "User-Agent": _USER_AGENT,
159
+ "Accept": "application/json",
160
+ })
161
+ return session
162
+
163
+ def _headers(self, authenticated):
164
+ # type: (bool) -> dict
165
+ """Return a *new* headers dict -- never mutates the session."""
166
+ headers = {"Content-Type": "application/json"}
167
+ if authenticated:
168
+ headers["Authorization"] = "Bearer {}".format(self._api_key)
169
+ return headers
170
+
171
+ def _handle_response(self, response):
172
+ # type: (requests.Response) -> dict
173
+ """Raise a typed exception on non-2xx or return the parsed JSON."""
174
+ if response.ok:
175
+ return response.json()
176
+
177
+ # Try to pull the structured detail from the API error envelope.
178
+ try:
179
+ body = response.json()
180
+ detail = body.get("detail", response.text)
181
+ except (ValueError, AttributeError):
182
+ detail = response.text
183
+
184
+ raise _map_error(response.status_code, str(detail))
185
+
186
+ def _raw_request(self, method, path, json_body=None, params=None, authenticated=True):
187
+ # type: (str, str, dict, dict, bool) -> dict
188
+ url = "{}{}".format(self._api_url, path)
189
+ headers = self._headers(authenticated)
190
+
191
+ try:
192
+ response = self._session.request(
193
+ method,
194
+ url,
195
+ json=json_body,
196
+ params=params,
197
+ headers=headers,
198
+ timeout=_REQUEST_TIMEOUT,
199
+ )
200
+ except requests.ConnectionError as exc:
201
+ raise NetworkError("Connection failed: {}".format(exc))
202
+ except requests.Timeout as exc:
203
+ raise NetworkError("Request timed out: {}".format(exc))
204
+ except requests.RequestException as exc:
205
+ raise NetworkError("Request error: {}".format(exc))
206
+
207
+ return self._handle_response(response)
208
+
209
+ def _raw_get(self, path, params=None, authenticated=True):
210
+ # type: (str, dict, bool) -> dict
211
+ return self._raw_request("GET", path, params=params, authenticated=authenticated)
212
+
213
+ def _raw_post(self, path, json_body=None, authenticated=True):
214
+ # type: (str, dict, bool) -> dict
215
+ return self._raw_request("POST", path, json_body=json_body, authenticated=authenticated)
216
+
217
+ # -- Public API ---------------------------------------------------------
218
+
219
+ def deposit_address(self):
220
+ # type: () -> str
221
+ """Return the XMR deposit address for this agent.
222
+
223
+ Calls ``POST /v2/balance/deposit`` and returns the address string.
224
+ """
225
+ data = self._raw_post("/v2/balance/deposit", json_body={})
226
+ return data["deposit_address"]
227
+
228
+ def pay(self, agent_name, amount, memo=None):
229
+ # type: (str, float, str) -> dict
230
+ """Send a hub-routed payment to *agent_name*.
231
+
232
+ Parameters
233
+ ----------
234
+ agent_name : str
235
+ Recipient agent's registered name.
236
+ amount : float
237
+ Amount in XMR. Converted to string for the API.
238
+ memo : str, optional
239
+ Human-readable note attached to the payment.
240
+
241
+ Returns
242
+ -------
243
+ dict
244
+ Full payment receipt from the API.
245
+ """
246
+ payload = {
247
+ "to_agent_name": agent_name,
248
+ "amount": str(amount),
249
+ "urgency": "normal",
250
+ }
251
+ if memo is not None:
252
+ payload["memo"] = memo
253
+
254
+ return self._raw_post("/v2/payments/hub-routing", json_body=payload)
255
+
256
+ def balance(self):
257
+ # type: () -> dict
258
+ """Return balance information for the authenticated agent.
259
+
260
+ Keys include ``available``, ``pending``, ``total_deposited``,
261
+ ``total_withdrawn``, ``deposit_address``, and ``token``.
262
+ """
263
+ return self._raw_get("/v2/balance")
264
+
265
+ def find_agents(self, capability=None, **kwargs):
266
+ # type: (str, ...) -> list
267
+ """Discover registered agents.
268
+
269
+ Parameters
270
+ ----------
271
+ capability : str, optional
272
+ Currently reserved for future filtering.
273
+ **kwargs
274
+ Additional query params forwarded to ``GET /v2/agents``, e.g.
275
+ ``limit``, ``offset``, ``min_trust_score``, ``tier``,
276
+ ``verified_only``.
277
+
278
+ Returns
279
+ -------
280
+ list
281
+ List of agent profile dicts.
282
+ """
283
+ params = {}
284
+ for key, value in kwargs.items():
285
+ if value is not None:
286
+ params[key] = value
287
+
288
+ data = self._raw_get("/v2/agents", params=params, authenticated=False)
289
+
290
+ # The endpoint may return the list directly or inside an envelope.
291
+ if isinstance(data, list):
292
+ return data
293
+ if isinstance(data, dict) and "agents" in data:
294
+ return data["agents"]
295
+ return data
296
+
297
+ def me(self):
298
+ # type: () -> dict
299
+ """Return the profile of the currently authenticated agent."""
300
+ return self._raw_get("/v2/agents/me")
301
+
302
+ def withdraw(self, amount, address):
303
+ # type: (float, str) -> dict
304
+ """Withdraw XMR to an external Monero address.
305
+
306
+ Parameters
307
+ ----------
308
+ amount : float
309
+ Amount in XMR. Converted to string for the API.
310
+ address : str
311
+ Destination Monero address.
312
+
313
+ Returns
314
+ -------
315
+ dict
316
+ Withdrawal receipt including ``tx_hash``, ``amount``, ``fee``.
317
+ """
318
+ payload = {
319
+ "amount": str(amount),
320
+ "address": address,
321
+ }
322
+ return self._raw_post("/v2/balance/withdraw", json_body=payload)
323
+
324
+ def payment_history(self, direction=None, limit=50):
325
+ # type: (str, int) -> list
326
+ """Retrieve payment history.
327
+
328
+ Parameters
329
+ ----------
330
+ direction : str, optional
331
+ ``"in"`` for received, ``"out"`` for sent, or ``None`` for both.
332
+ limit : int
333
+ Maximum number of records (default 50).
334
+
335
+ Returns
336
+ -------
337
+ list
338
+ List of payment record dicts.
339
+ """
340
+ params = {"limit": limit}
341
+ if direction is not None:
342
+ params["direction"] = direction
343
+
344
+ data = self._raw_get("/v2/payments/history", params=params)
345
+
346
+ if isinstance(data, list):
347
+ return data
348
+ if isinstance(data, dict) and "payments" in data:
349
+ return data["payments"]
350
+ return data
@@ -0,0 +1,63 @@
1
+ """Typed exceptions for Sthrip SDK.
2
+
3
+ All exceptions carry ``status_code`` and ``detail`` so callers can
4
+ inspect the failure programmatically without parsing strings.
5
+ """
6
+
7
+
8
+ class StrhipError(Exception):
9
+ """Base exception for every Sthrip SDK error."""
10
+
11
+ def __init__(self, detail, status_code=None):
12
+ # type: (str, int) -> None
13
+ super(StrhipError, self).__init__(detail)
14
+ self.detail = detail
15
+ self.status_code = status_code
16
+
17
+
18
+ class AuthError(StrhipError):
19
+ """Raised on 401 Unauthorized or 403 Forbidden."""
20
+
21
+ def __init__(self, detail="Authentication failed", status_code=401):
22
+ # type: (str, int) -> None
23
+ super(AuthError, self).__init__(detail, status_code)
24
+
25
+
26
+ class PaymentError(StrhipError):
27
+ """Generic payment failure."""
28
+
29
+ def __init__(self, detail="Payment failed", status_code=None):
30
+ # type: (str, int) -> None
31
+ super(PaymentError, self).__init__(detail, status_code)
32
+
33
+
34
+ class InsufficientBalance(PaymentError):
35
+ """Not enough XMR to complete the payment or withdrawal."""
36
+
37
+ def __init__(self, detail="Insufficient balance", status_code=None):
38
+ # type: (str, int) -> None
39
+ super(InsufficientBalance, self).__init__(detail, status_code)
40
+
41
+
42
+ class AgentNotFound(PaymentError):
43
+ """Recipient agent does not exist."""
44
+
45
+ def __init__(self, detail="Agent not found", status_code=404):
46
+ # type: (str, int) -> None
47
+ super(AgentNotFound, self).__init__(detail, status_code)
48
+
49
+
50
+ class RateLimitError(StrhipError):
51
+ """Too many requests (HTTP 429)."""
52
+
53
+ def __init__(self, detail="Rate limit exceeded", status_code=429):
54
+ # type: (str, int) -> None
55
+ super(RateLimitError, self).__init__(detail, status_code)
56
+
57
+
58
+ class NetworkError(StrhipError):
59
+ """Connection or timeout failure talking to the Sthrip API."""
60
+
61
+ def __init__(self, detail="Network error", status_code=None):
62
+ # type: (str, int) -> None
63
+ super(NetworkError, self).__init__(detail, status_code)
@@ -0,0 +1,83 @@
1
+ Metadata-Version: 2.4
2
+ Name: sthrip
3
+ Version: 0.1.0
4
+ Summary: Anonymous payments for AI agents
5
+ License: MIT
6
+ Project-URL: Homepage, https://sthrip.dev
7
+ Project-URL: Documentation, https://sthrip-api-production.up.railway.app/docs
8
+ Project-URL: Repository, https://github.com/sthrip/sthrip
9
+ Keywords: payments,ai,agents,monero,anonymous
10
+ Classifier: Development Status :: 4 - Beta
11
+ Classifier: Intended Audience :: Developers
12
+ Classifier: License :: OSI Approved :: MIT License
13
+ Classifier: Programming Language :: Python :: 3
14
+ Requires-Python: >=3.8
15
+ Description-Content-Type: text/markdown
16
+ License-File: LICENSE
17
+ Requires-Dist: requests>=2.25
18
+ Dynamic: license-file
19
+
20
+ # sthrip
21
+
22
+ > Anonymous payments for AI agents.
23
+
24
+ [![PyPI version](https://img.shields.io/pypi/v/sthrip)](https://pypi.org/project/sthrip/)
25
+ [![Python 3.8+](https://img.shields.io/badge/python-3.8%2B-blue)](https://www.python.org/downloads/)
26
+ [![License: MIT](https://img.shields.io/badge/License-MIT-yellow.svg)](https://opensource.org/licenses/MIT)
27
+
28
+ ## Quickstart
29
+
30
+ ```python
31
+ from sthrip import Sthrip
32
+
33
+ s = Sthrip() # auto-registers on first use
34
+ s.pay("agent-name", 0.5) # send 0.5 XMR
35
+ ```
36
+
37
+ ## Install
38
+
39
+ ```
40
+ pip install sthrip
41
+ ```
42
+
43
+ ## How it works
44
+
45
+ Agents register and get a unique XMR deposit address. Deposit Monero to the address, then pay other agents through the hub with a single method call. Payments are routed internally -- instant, private, and charged at 0.1% per transaction. The entire system is built on Monero for maximum privacy. No accounts, no KYC, no tracking.
46
+
47
+ ## API Reference
48
+
49
+ | Method | Description |
50
+ |--------|-------------|
51
+ | `Sthrip()` | Initialize client (auto-registers if no credentials found) |
52
+ | `s.deposit_address()` | Get your XMR deposit address |
53
+ | `s.pay(agent, amount)` | Send payment to another agent |
54
+ | `s.balance()` | Check your current balance |
55
+ | `s.find_agents()` | Discover available agents |
56
+ | `s.me()` | View your agent profile |
57
+ | `s.withdraw(amount, addr)` | Withdraw XMR to an external address |
58
+ | `s.payment_history()` | View transaction history |
59
+
60
+ ## Configuration
61
+
62
+ | Variable | Purpose |
63
+ |----------|---------|
64
+ | `STHRIP_API_KEY` | API key for authentication |
65
+ | `STHRIP_API_URL` | Custom API URL (optional) |
66
+
67
+ Credentials are auto-saved to `~/.sthrip/credentials.json` on first registration.
68
+
69
+ ## Fees
70
+
71
+ - **0.1%** per hub-routed payment
72
+ - No registration fee
73
+ - No deposit fee
74
+
75
+ ## Links
76
+
77
+ - [Landing page](https://sthrip.dev)
78
+ - [API Docs](https://sthrip-api-production.up.railway.app/docs)
79
+ - [PyPI](https://pypi.org/project/sthrip/)
80
+
81
+ ## License
82
+
83
+ [MIT](LICENSE)
@@ -0,0 +1,12 @@
1
+ LICENSE
2
+ README.md
3
+ pyproject.toml
4
+ sthrip/__init__.py
5
+ sthrip/auth.py
6
+ sthrip/client.py
7
+ sthrip/exceptions.py
8
+ sthrip.egg-info/PKG-INFO
9
+ sthrip.egg-info/SOURCES.txt
10
+ sthrip.egg-info/dependency_links.txt
11
+ sthrip.egg-info/requires.txt
12
+ sthrip.egg-info/top_level.txt
@@ -0,0 +1 @@
1
+ requests>=2.25
@@ -0,0 +1 @@
1
+ sthrip