satgate 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.
- satgate-0.1.0/PKG-INFO +121 -0
- satgate-0.1.0/README.md +76 -0
- satgate-0.1.0/satgate/__init__.py +4 -0
- satgate-0.1.0/satgate/client.py +99 -0
- satgate-0.1.0/satgate/langchain_integrations.py +46 -0
- satgate-0.1.0/satgate.egg-info/PKG-INFO +121 -0
- satgate-0.1.0/satgate.egg-info/SOURCES.txt +11 -0
- satgate-0.1.0/satgate.egg-info/dependency_links.txt +1 -0
- satgate-0.1.0/satgate.egg-info/requires.txt +10 -0
- satgate-0.1.0/satgate.egg-info/top_level.txt +1 -0
- satgate-0.1.0/setup.cfg +4 -0
- satgate-0.1.0/setup.py +41 -0
- satgate-0.1.0/tests/test_sdk.py +107 -0
satgate-0.1.0/PKG-INFO
ADDED
|
@@ -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
|
+
[](https://pypi.org/project/satgate/)
|
|
51
|
+
[](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
|
+
|
satgate-0.1.0/README.md
ADDED
|
@@ -0,0 +1,76 @@
|
|
|
1
|
+
# SatGate Python SDK
|
|
2
|
+
|
|
3
|
+
**Stripe for AI Agents** — L402 micropayments for APIs.
|
|
4
|
+
|
|
5
|
+
[](https://pypi.org/project/satgate/)
|
|
6
|
+
[](https://opensource.org/licenses/MIT)
|
|
7
|
+
|
|
8
|
+
## Installation
|
|
9
|
+
|
|
10
|
+
```bash
|
|
11
|
+
pip install satgate
|
|
12
|
+
```
|
|
13
|
+
|
|
14
|
+
For LangChain integration:
|
|
15
|
+
|
|
16
|
+
```bash
|
|
17
|
+
pip install satgate[langchain]
|
|
18
|
+
```
|
|
19
|
+
|
|
20
|
+
## Quick Start
|
|
21
|
+
|
|
22
|
+
```python
|
|
23
|
+
from satgate import SatGateSession, LightningWallet
|
|
24
|
+
|
|
25
|
+
# Implement your wallet (or use a library like lndgrpc, pyln-client)
|
|
26
|
+
class MyWallet(LightningWallet):
|
|
27
|
+
def pay_invoice(self, invoice: str) -> str:
|
|
28
|
+
# Pay the invoice and return the preimage
|
|
29
|
+
return your_lightning_node.pay(invoice)
|
|
30
|
+
|
|
31
|
+
# Create a session that auto-pays 402 responses
|
|
32
|
+
session = SatGateSession(wallet=MyWallet())
|
|
33
|
+
|
|
34
|
+
# Use like requests - payments happen automatically!
|
|
35
|
+
response = session.get("https://api.example.com/premium/data")
|
|
36
|
+
print(response.json())
|
|
37
|
+
```
|
|
38
|
+
|
|
39
|
+
## LangChain Integration
|
|
40
|
+
|
|
41
|
+
```python
|
|
42
|
+
from satgate import SatGateTool
|
|
43
|
+
|
|
44
|
+
# Create a tool your AI agent can use
|
|
45
|
+
tool = SatGateTool(wallet=MyWallet())
|
|
46
|
+
|
|
47
|
+
# Add to your LangChain agent
|
|
48
|
+
agent = initialize_agent(
|
|
49
|
+
tools=[tool],
|
|
50
|
+
llm=ChatOpenAI(),
|
|
51
|
+
agent=AgentType.OPENAI_FUNCTIONS
|
|
52
|
+
)
|
|
53
|
+
|
|
54
|
+
# The agent can now access paid APIs automatically!
|
|
55
|
+
agent.run("Fetch premium market data from https://api.example.com/insights")
|
|
56
|
+
```
|
|
57
|
+
|
|
58
|
+
## How It Works
|
|
59
|
+
|
|
60
|
+
1. Your code makes a request to a paid API
|
|
61
|
+
2. The API returns `402 Payment Required` with a Lightning invoice
|
|
62
|
+
3. SatGate automatically pays the invoice via your wallet
|
|
63
|
+
4. The request is retried with the L402 token
|
|
64
|
+
5. You get your data ✨
|
|
65
|
+
|
|
66
|
+
## Links
|
|
67
|
+
|
|
68
|
+
- 🌐 Website: [satgate.io](https://satgate.io)
|
|
69
|
+
- 📖 Playground: [satgate.io/playground](https://satgate.io/playground)
|
|
70
|
+
- 💻 GitHub: [github.com/SatGate-io/satgate](https://github.com/SatGate-io/satgate)
|
|
71
|
+
- 📧 Contact: contact@satgate.io
|
|
72
|
+
|
|
73
|
+
## License
|
|
74
|
+
|
|
75
|
+
MIT License - © 2025 SatGate. Patent Pending.
|
|
76
|
+
|
|
@@ -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
|
+
[](https://pypi.org/project/satgate/)
|
|
51
|
+
[](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,11 @@
|
|
|
1
|
+
README.md
|
|
2
|
+
setup.py
|
|
3
|
+
satgate/__init__.py
|
|
4
|
+
satgate/client.py
|
|
5
|
+
satgate/langchain_integrations.py
|
|
6
|
+
satgate.egg-info/PKG-INFO
|
|
7
|
+
satgate.egg-info/SOURCES.txt
|
|
8
|
+
satgate.egg-info/dependency_links.txt
|
|
9
|
+
satgate.egg-info/requires.txt
|
|
10
|
+
satgate.egg-info/top_level.txt
|
|
11
|
+
tests/test_sdk.py
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
satgate
|
satgate-0.1.0/setup.cfg
ADDED
satgate-0.1.0/setup.py
ADDED
|
@@ -0,0 +1,41 @@
|
|
|
1
|
+
from setuptools import setup, find_packages
|
|
2
|
+
|
|
3
|
+
setup(
|
|
4
|
+
name="satgate",
|
|
5
|
+
version="0.1.0",
|
|
6
|
+
description="Python SDK for SatGate - Stripe for AI Agents. L402 micropayments for APIs.",
|
|
7
|
+
long_description=open("README.md").read() if __import__("os").path.exists("README.md") else "",
|
|
8
|
+
long_description_content_type="text/markdown",
|
|
9
|
+
author="SatGate Team",
|
|
10
|
+
author_email="contact@satgate.io",
|
|
11
|
+
url="https://github.com/SatGate-io/satgate",
|
|
12
|
+
project_urls={
|
|
13
|
+
"Homepage": "https://satgate.io",
|
|
14
|
+
"Documentation": "https://satgate.io/playground",
|
|
15
|
+
"Repository": "https://github.com/SatGate-io/satgate",
|
|
16
|
+
},
|
|
17
|
+
packages=find_packages(),
|
|
18
|
+
install_requires=[
|
|
19
|
+
"requests>=2.25.0",
|
|
20
|
+
],
|
|
21
|
+
extras_require={
|
|
22
|
+
"langchain": ["langchain>=0.1.0", "pydantic>=2.0.0"],
|
|
23
|
+
"dev": ["pytest", "responses", "flask"],
|
|
24
|
+
},
|
|
25
|
+
python_requires=">=3.8",
|
|
26
|
+
classifiers=[
|
|
27
|
+
"Development Status :: 4 - Beta",
|
|
28
|
+
"Intended Audience :: Developers",
|
|
29
|
+
"License :: OSI Approved :: MIT License",
|
|
30
|
+
"Programming Language :: Python :: 3",
|
|
31
|
+
"Programming Language :: Python :: 3.8",
|
|
32
|
+
"Programming Language :: Python :: 3.9",
|
|
33
|
+
"Programming Language :: Python :: 3.10",
|
|
34
|
+
"Programming Language :: Python :: 3.11",
|
|
35
|
+
"Programming Language :: Python :: 3.12",
|
|
36
|
+
"Topic :: Internet :: WWW/HTTP",
|
|
37
|
+
"Topic :: Software Development :: Libraries :: Python Modules",
|
|
38
|
+
],
|
|
39
|
+
keywords="l402 lightning bitcoin micropayments api ai agents langchain",
|
|
40
|
+
)
|
|
41
|
+
|
|
@@ -0,0 +1,107 @@
|
|
|
1
|
+
import threading
|
|
2
|
+
import time
|
|
3
|
+
import requests
|
|
4
|
+
import unittest
|
|
5
|
+
from http.server import HTTPServer, BaseHTTPRequestHandler
|
|
6
|
+
from satgate.client import SatGateSession, LightningWallet
|
|
7
|
+
|
|
8
|
+
# --- Mock Server ---
|
|
9
|
+
|
|
10
|
+
class MockL402Handler(BaseHTTPRequestHandler):
|
|
11
|
+
protocol_version = 'HTTP/1.1'
|
|
12
|
+
|
|
13
|
+
def do_GET(self):
|
|
14
|
+
# Check for Authorization header
|
|
15
|
+
auth_header = self.headers.get("Authorization")
|
|
16
|
+
|
|
17
|
+
if auth_header and auth_header.startswith("L402"):
|
|
18
|
+
# Validate token (Mock validation)
|
|
19
|
+
# Expected: L402 macaroon:preimage
|
|
20
|
+
token = auth_header.split(" ")[1]
|
|
21
|
+
if ":" in token:
|
|
22
|
+
macaroon, preimage = token.split(":")
|
|
23
|
+
if macaroon == "test_macaroon" and preimage == "test_preimage":
|
|
24
|
+
self.send_response(200)
|
|
25
|
+
self.send_header("Content-type", "application/json")
|
|
26
|
+
self.end_headers()
|
|
27
|
+
self.wfile.write(b'{"status": "success", "data": "premium_content"}')
|
|
28
|
+
return
|
|
29
|
+
|
|
30
|
+
# Default: Return 402
|
|
31
|
+
self.send_response(402)
|
|
32
|
+
self.send_header("WWW-Authenticate", 'L402 macaroon="test_macaroon", invoice="lnbc_test_invoice"')
|
|
33
|
+
self.send_header("Content-type", "text/plain")
|
|
34
|
+
self.end_headers()
|
|
35
|
+
self.wfile.write(b'Payment Required')
|
|
36
|
+
|
|
37
|
+
def log_message(self, format, *args):
|
|
38
|
+
# Silence logs
|
|
39
|
+
pass
|
|
40
|
+
|
|
41
|
+
class MockServer:
|
|
42
|
+
def __init__(self):
|
|
43
|
+
self.server = HTTPServer(('localhost', 0), MockL402Handler)
|
|
44
|
+
self.port = self.server.server_port
|
|
45
|
+
self.thread = threading.Thread(target=self.server.serve_forever)
|
|
46
|
+
self.thread.daemon = True
|
|
47
|
+
|
|
48
|
+
def start(self):
|
|
49
|
+
self.thread.start()
|
|
50
|
+
# Give it a moment to start
|
|
51
|
+
time.sleep(0.1)
|
|
52
|
+
|
|
53
|
+
def stop(self):
|
|
54
|
+
self.server.shutdown()
|
|
55
|
+
self.server.server_close()
|
|
56
|
+
|
|
57
|
+
# --- Mock Wallet ---
|
|
58
|
+
|
|
59
|
+
class MockWallet(LightningWallet):
|
|
60
|
+
def pay_invoice(self, invoice: str) -> str:
|
|
61
|
+
if invoice == "lnbc_test_invoice":
|
|
62
|
+
return "test_preimage"
|
|
63
|
+
raise ValueError("Unknown invoice")
|
|
64
|
+
|
|
65
|
+
# --- Integration Test ---
|
|
66
|
+
|
|
67
|
+
class TestSatGateSDK(unittest.TestCase):
|
|
68
|
+
@classmethod
|
|
69
|
+
def setUpClass(cls):
|
|
70
|
+
cls.server = MockServer()
|
|
71
|
+
cls.server.start()
|
|
72
|
+
cls.base_url = f"http://localhost:{cls.server.port}"
|
|
73
|
+
|
|
74
|
+
@classmethod
|
|
75
|
+
def tearDownClass(cls):
|
|
76
|
+
cls.server.stop()
|
|
77
|
+
|
|
78
|
+
def test_l402_flow(self):
|
|
79
|
+
# 1. Setup Wallet and Session
|
|
80
|
+
wallet = MockWallet()
|
|
81
|
+
session = SatGateSession(wallet=wallet)
|
|
82
|
+
|
|
83
|
+
# 2. Make Request
|
|
84
|
+
print(f"\nTesting request to {self.base_url}...")
|
|
85
|
+
response = session.get(self.base_url)
|
|
86
|
+
|
|
87
|
+
# 3. Verify Result
|
|
88
|
+
self.assertEqual(response.status_code, 200)
|
|
89
|
+
self.assertEqual(response.json(), {"status": "success", "data": "premium_content"})
|
|
90
|
+
print("✅ L402 Flow Verified: 402 -> Pay -> 200")
|
|
91
|
+
|
|
92
|
+
def test_payment_failure(self):
|
|
93
|
+
# Wallet that fails
|
|
94
|
+
class BrokeWallet(LightningWallet):
|
|
95
|
+
def pay_invoice(self, invoice: str) -> str:
|
|
96
|
+
raise Exception("Insufficient funds")
|
|
97
|
+
|
|
98
|
+
session = SatGateSession(wallet=BrokeWallet())
|
|
99
|
+
|
|
100
|
+
# Should return the original 402 response
|
|
101
|
+
response = session.get(self.base_url)
|
|
102
|
+
self.assertEqual(response.status_code, 402)
|
|
103
|
+
print("✅ Failure Handling Verified")
|
|
104
|
+
|
|
105
|
+
if __name__ == "__main__":
|
|
106
|
+
unittest.main()
|
|
107
|
+
|