satgate 0.1.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.
satgate/__init__.py ADDED
@@ -0,0 +1,4 @@
1
+ from .client import SatGateSession, LightningWallet
2
+
3
+ __all__ = ["SatGateSession", "LightningWallet"]
4
+
satgate/client.py ADDED
@@ -0,0 +1,99 @@
1
+ import requests
2
+ import re
3
+ from abc import ABC, abstractmethod
4
+ from typing import Optional, Dict, Any
5
+
6
+ # --- 1. Wallet Interface (Plug & Play) ---
7
+
8
+ class LightningWallet(ABC):
9
+ """Abstract Base Class for any Lightning Wallet"""
10
+
11
+ @abstractmethod
12
+ def pay_invoice(self, invoice: str) -> str:
13
+ """Pays the invoice and returns the preimage (hex string)"""
14
+ pass
15
+
16
+ # --- 2. The Intelligent Session ---
17
+
18
+ class SatGateSession(requests.Session):
19
+ def __init__(self, wallet: LightningWallet):
20
+ super().__init__()
21
+ self.wallet = wallet
22
+
23
+ def request(self, method: str, url: str, *args, **kwargs) -> requests.Response:
24
+ # 1. Attempt the request normally
25
+ try:
26
+ response = super().request(method, url, *args, **kwargs)
27
+ except requests.exceptions.RequestException as e:
28
+ # If we can't even connect, re-raise
29
+ raise e
30
+
31
+ # 2. Intercept 402 Errors
32
+ if response.status_code == 402:
33
+ return self._handle_payment_flow(response, method, url, *args, **kwargs)
34
+
35
+ return response
36
+
37
+ def _handle_payment_flow(self, response: requests.Response, method: str, url: str, *args, **kwargs) -> requests.Response:
38
+ """The magic loop: Parse -> Pay -> Retry"""
39
+
40
+ # A. Parse the L402 Header
41
+ auth_header = response.headers.get("WWW-Authenticate")
42
+ if not auth_header:
43
+ # Some servers might not send WWW-Authenticate or send it differently
44
+ # Try to see if it's in the body or standard L402
45
+ print("⚠️ 402 received but no WWW-Authenticate header found.")
46
+ return response
47
+
48
+ # Check for L402 or LSAT
49
+ if "L402" not in auth_header and "LSAT" not in auth_header:
50
+ print("⚠️ 402 received but header does not contain L402/LSAT scheme.")
51
+ return response
52
+
53
+ # Extract Invoice and Macaroon (Regex to handle standard L402 format)
54
+ # Example: L402 macaroon="...", invoice="lnbc..."
55
+ # Using non-greedy match for values in quotes
56
+ macaroon_match = re.search(r'macaroon="([^"]+)"', auth_header)
57
+ invoice_match = re.search(r'invoice="([^"]+)"', auth_header)
58
+
59
+ if not macaroon_match or not invoice_match:
60
+ print("❌ Invalid L402 header format: could not find macaroon or invoice.")
61
+ return response
62
+
63
+ macaroon = macaroon_match.group(1)
64
+ invoice = invoice_match.group(1)
65
+
66
+ print(f"⚡ 402 Detected. Price: Unknown (check invoice).")
67
+ print(f" Invoice: {invoice[:20]}...{invoice[-10:]}")
68
+
69
+ # B. Pay the Invoice (User's Wallet Implementation)
70
+ try:
71
+ preimage = self.wallet.pay_invoice(invoice)
72
+ if not preimage:
73
+ raise ValueError("Wallet returned empty preimage")
74
+ print(f"✅ Payment Confirmed. Preimage: {preimage[:10]}...")
75
+ except Exception as e:
76
+ print(f"❌ Payment Failed: {e}")
77
+ return response
78
+
79
+ # C. Retry with Authorization
80
+ # Format: Authorization: L402 <macaroon>:<preimage>
81
+ # Note: Some implementations might use LSAT, but L402 is the standard.
82
+ # We'll use L402 as the prefix.
83
+ l402_token = f"L402 {macaroon}:{preimage}"
84
+
85
+ # Merge headers if they exist, or create new dict
86
+ # We need to be careful not to modify the original kwargs['headers'] in place
87
+ # if it's reused elsewhere, though usually fine here.
88
+ req_headers = kwargs.get("headers", {})
89
+ if req_headers is None:
90
+ req_headers = {}
91
+
92
+ # Copy to avoid side effects
93
+ req_headers = req_headers.copy()
94
+ req_headers["Authorization"] = l402_token
95
+ kwargs["headers"] = req_headers
96
+
97
+ print("🔄 Retrying request with L402 Token...")
98
+ return super().request(method, url, *args, **kwargs)
99
+
@@ -0,0 +1,46 @@
1
+ from typing import Optional, Type, Any
2
+ from langchain.tools import BaseTool
3
+ from pydantic import BaseModel, Field
4
+
5
+ from .client import SatGateSession, LightningWallet
6
+
7
+ class SatGateToolInput(BaseModel):
8
+ endpoint: str = Field(description="The full URL of the premium API endpoint to fetch data from.")
9
+
10
+ class SatGateTool(BaseTool):
11
+ name: str = "satgate_api_browser"
12
+ description: str = (
13
+ "Useful for fetching data from paid/premium APIs that require Lightning Network payments. "
14
+ "Use this tool when you need to access high-value data, reports, or analytics "
15
+ "that are behind a paywall. The tool handles payment automatically."
16
+ )
17
+ args_schema: Type[BaseModel] = SatGateToolInput
18
+
19
+ # We exclude session from Pydantic fields since it's not a model field
20
+ # but an internal component. However, LangChain tools often want fields to be Pydantic compatible.
21
+ # We'll mark it as PrivateAttr or just exclude it from init if possible,
22
+ # but BaseTool inherits from BaseModel.
23
+ # The standard way is to treat it as a private attribute or configured via init.
24
+
25
+ _session: SatGateSession
26
+
27
+ def __init__(self, wallet: LightningWallet, **kwargs):
28
+ super().__init__(**kwargs)
29
+ self._session = SatGateSession(wallet=wallet)
30
+
31
+ def _run(self, endpoint: str) -> str:
32
+ """Synchronous execution"""
33
+ try:
34
+ response = self._session.get(endpoint)
35
+ # Raise error for 4xx/5xx if not handled (though 402 is handled inside)
36
+ response.raise_for_status()
37
+ return response.text
38
+ except Exception as e:
39
+ return f"Error fetching data: {str(e)}"
40
+
41
+ async def _arun(self, endpoint: str) -> str:
42
+ """Async support (Critical for high-performance agents)"""
43
+ # For MVP, we can just wrap the sync call or use aiohttp later
44
+ # Since SatGateSession is sync (requests), we just call _run.
45
+ return self._run(endpoint)
46
+
@@ -0,0 +1,121 @@
1
+ Metadata-Version: 2.4
2
+ Name: satgate
3
+ Version: 0.1.0
4
+ Summary: Python SDK for SatGate - Stripe for AI Agents. L402 micropayments for APIs.
5
+ Home-page: https://github.com/SatGate-io/satgate
6
+ Author: SatGate Team
7
+ Author-email: contact@satgate.io
8
+ Project-URL: Homepage, https://satgate.io
9
+ Project-URL: Documentation, https://satgate.io/playground
10
+ Project-URL: Repository, https://github.com/SatGate-io/satgate
11
+ Keywords: l402 lightning bitcoin micropayments api ai agents langchain
12
+ Classifier: Development Status :: 4 - Beta
13
+ Classifier: Intended Audience :: Developers
14
+ Classifier: License :: OSI Approved :: MIT License
15
+ Classifier: Programming Language :: Python :: 3
16
+ Classifier: Programming Language :: Python :: 3.8
17
+ Classifier: Programming Language :: Python :: 3.9
18
+ Classifier: Programming Language :: Python :: 3.10
19
+ Classifier: Programming Language :: Python :: 3.11
20
+ Classifier: Programming Language :: Python :: 3.12
21
+ Classifier: Topic :: Internet :: WWW/HTTP
22
+ Classifier: Topic :: Software Development :: Libraries :: Python Modules
23
+ Requires-Python: >=3.8
24
+ Description-Content-Type: text/markdown
25
+ Requires-Dist: requests>=2.25.0
26
+ Provides-Extra: langchain
27
+ Requires-Dist: langchain>=0.1.0; extra == "langchain"
28
+ Requires-Dist: pydantic>=2.0.0; extra == "langchain"
29
+ Provides-Extra: dev
30
+ Requires-Dist: pytest; extra == "dev"
31
+ Requires-Dist: responses; extra == "dev"
32
+ Requires-Dist: flask; extra == "dev"
33
+ Dynamic: author
34
+ Dynamic: author-email
35
+ Dynamic: classifier
36
+ Dynamic: description
37
+ Dynamic: description-content-type
38
+ Dynamic: home-page
39
+ Dynamic: keywords
40
+ Dynamic: project-url
41
+ Dynamic: provides-extra
42
+ Dynamic: requires-dist
43
+ Dynamic: requires-python
44
+ Dynamic: summary
45
+
46
+ # SatGate Python SDK
47
+
48
+ **Stripe for AI Agents** — L402 micropayments for APIs.
49
+
50
+ [![PyPI version](https://badge.fury.io/py/satgate.svg)](https://pypi.org/project/satgate/)
51
+ [![License: MIT](https://img.shields.io/badge/License-MIT-yellow.svg)](https://opensource.org/licenses/MIT)
52
+
53
+ ## Installation
54
+
55
+ ```bash
56
+ pip install satgate
57
+ ```
58
+
59
+ For LangChain integration:
60
+
61
+ ```bash
62
+ pip install satgate[langchain]
63
+ ```
64
+
65
+ ## Quick Start
66
+
67
+ ```python
68
+ from satgate import SatGateSession, LightningWallet
69
+
70
+ # Implement your wallet (or use a library like lndgrpc, pyln-client)
71
+ class MyWallet(LightningWallet):
72
+ def pay_invoice(self, invoice: str) -> str:
73
+ # Pay the invoice and return the preimage
74
+ return your_lightning_node.pay(invoice)
75
+
76
+ # Create a session that auto-pays 402 responses
77
+ session = SatGateSession(wallet=MyWallet())
78
+
79
+ # Use like requests - payments happen automatically!
80
+ response = session.get("https://api.example.com/premium/data")
81
+ print(response.json())
82
+ ```
83
+
84
+ ## LangChain Integration
85
+
86
+ ```python
87
+ from satgate import SatGateTool
88
+
89
+ # Create a tool your AI agent can use
90
+ tool = SatGateTool(wallet=MyWallet())
91
+
92
+ # Add to your LangChain agent
93
+ agent = initialize_agent(
94
+ tools=[tool],
95
+ llm=ChatOpenAI(),
96
+ agent=AgentType.OPENAI_FUNCTIONS
97
+ )
98
+
99
+ # The agent can now access paid APIs automatically!
100
+ agent.run("Fetch premium market data from https://api.example.com/insights")
101
+ ```
102
+
103
+ ## How It Works
104
+
105
+ 1. Your code makes a request to a paid API
106
+ 2. The API returns `402 Payment Required` with a Lightning invoice
107
+ 3. SatGate automatically pays the invoice via your wallet
108
+ 4. The request is retried with the L402 token
109
+ 5. You get your data ✨
110
+
111
+ ## Links
112
+
113
+ - 🌐 Website: [satgate.io](https://satgate.io)
114
+ - 📖 Playground: [satgate.io/playground](https://satgate.io/playground)
115
+ - 💻 GitHub: [github.com/SatGate-io/satgate](https://github.com/SatGate-io/satgate)
116
+ - 📧 Contact: contact@satgate.io
117
+
118
+ ## License
119
+
120
+ MIT License - © 2025 SatGate. Patent Pending.
121
+
@@ -0,0 +1,7 @@
1
+ satgate/__init__.py,sha256=KxzSzbD50GKEao2piywGDIiMQKCcS2FRvnVFf5p_TtA,102
2
+ satgate/client.py,sha256=RgsAmdPIBTHM6uLy8GdskZtzhL5hQxIzaAAlcJtzZng,3898
3
+ satgate/langchain_integrations.py,sha256=gugk-CfOnMFfIHvYNkl8dCZ3ulJG6dlP4_TNrssjUEE,1971
4
+ satgate-0.1.0.dist-info/METADATA,sha256=BkIWHaDJ2q2hKogAVXNp-VqAl0AqwBFqhOHeqPGmxMo,3608
5
+ satgate-0.1.0.dist-info/WHEEL,sha256=_zCd3N1l69ArxyTb8rzEoP9TpbYXkqRFSNOD5OuxnTs,91
6
+ satgate-0.1.0.dist-info/top_level.txt,sha256=7snZYk1LjcrpxhXVSGoAKTJBkisPVJq4YpGTpQtL_k8,8
7
+ satgate-0.1.0.dist-info/RECORD,,
@@ -0,0 +1,5 @@
1
+ Wheel-Version: 1.0
2
+ Generator: setuptools (80.9.0)
3
+ Root-Is-Purelib: true
4
+ Tag: py3-none-any
5
+
@@ -0,0 +1 @@
1
+ satgate