bridgenode 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.
- bridgenode-0.1.0/PKG-INFO +67 -0
- bridgenode-0.1.0/README.md +42 -0
- bridgenode-0.1.0/bridgenode/__init__.py +10 -0
- bridgenode-0.1.0/bridgenode/auth.py +61 -0
- bridgenode-0.1.0/bridgenode/client.py +245 -0
- bridgenode-0.1.0/bridgenode/exceptions.py +20 -0
- bridgenode-0.1.0/bridgenode.egg-info/PKG-INFO +67 -0
- bridgenode-0.1.0/bridgenode.egg-info/SOURCES.txt +12 -0
- bridgenode-0.1.0/bridgenode.egg-info/dependency_links.txt +1 -0
- bridgenode-0.1.0/bridgenode.egg-info/entry_points.txt +2 -0
- bridgenode-0.1.0/bridgenode.egg-info/requires.txt +3 -0
- bridgenode-0.1.0/bridgenode.egg-info/top_level.txt +1 -0
- bridgenode-0.1.0/pyproject.toml +39 -0
- bridgenode-0.1.0/setup.cfg +4 -0
|
@@ -0,0 +1,67 @@
|
|
|
1
|
+
Metadata-Version: 2.4
|
|
2
|
+
Name: bridgenode
|
|
3
|
+
Version: 0.1.0
|
|
4
|
+
Summary: Official Python SDK for BridgeNode — automated x402 payment auth for AI agents
|
|
5
|
+
Author-email: BridgeNode <eli.BNx@proton.me>
|
|
6
|
+
License: MIT
|
|
7
|
+
Project-URL: Homepage, https://bridgenode.cc
|
|
8
|
+
Project-URL: Source, https://github.com/BridgeNode/x402-client
|
|
9
|
+
Project-URL: Tracker, https://github.com/BridgeNode/x402-client/issues
|
|
10
|
+
Keywords: x402,payment,solana,ed25519,ai,agent
|
|
11
|
+
Classifier: Development Status :: 4 - Beta
|
|
12
|
+
Classifier: Intended Audience :: Developers
|
|
13
|
+
Classifier: License :: OSI Approved :: MIT License
|
|
14
|
+
Classifier: Programming Language :: Python :: 3.10
|
|
15
|
+
Classifier: Programming Language :: Python :: 3.11
|
|
16
|
+
Classifier: Programming Language :: Python :: 3.12
|
|
17
|
+
Classifier: Programming Language :: Python :: 3.13
|
|
18
|
+
Classifier: Topic :: Internet :: WWW/HTTP
|
|
19
|
+
Classifier: Topic :: Security :: Cryptography
|
|
20
|
+
Requires-Python: >=3.10
|
|
21
|
+
Description-Content-Type: text/markdown
|
|
22
|
+
Requires-Dist: httpx>=0.28.0
|
|
23
|
+
Requires-Dist: pynacl>=1.6.0
|
|
24
|
+
Requires-Dist: base58>=2.1.0
|
|
25
|
+
|
|
26
|
+
# bridgenode
|
|
27
|
+
|
|
28
|
+
Official Python SDK for **BridgeNode** — automated x402 payment auth for AI agents.
|
|
29
|
+
|
|
30
|
+
`pip install bridgenode`
|
|
31
|
+
|
|
32
|
+
## Quick start
|
|
33
|
+
|
|
34
|
+
```python
|
|
35
|
+
from bridgenode import X402Client
|
|
36
|
+
|
|
37
|
+
client = X402Client()
|
|
38
|
+
print(f"My address: {client.address}")
|
|
39
|
+
|
|
40
|
+
response = await client.request(messages=[
|
|
41
|
+
{"role": "user", "content": "Hello, who are you?"}
|
|
42
|
+
])
|
|
43
|
+
print(response["choices"][0]["message"]["content"])
|
|
44
|
+
```
|
|
45
|
+
|
|
46
|
+
## How it works
|
|
47
|
+
|
|
48
|
+
1. Generates an ed25519 keypair (if none provided)
|
|
49
|
+
2. Signs each request with a UUID nonce
|
|
50
|
+
3. Sends X-Address / X-Signature / X-Nonce headers
|
|
51
|
+
4. Handles 402, 401, 409 responses automatically
|
|
52
|
+
|
|
53
|
+
## Install from source
|
|
54
|
+
|
|
55
|
+
```bash
|
|
56
|
+
cd bridgenode
|
|
57
|
+
pip install -e .
|
|
58
|
+
```
|
|
59
|
+
|
|
60
|
+
## Requirements
|
|
61
|
+
|
|
62
|
+
- Python ≥ 3.10
|
|
63
|
+
- httpx, pynacl, base58
|
|
64
|
+
|
|
65
|
+
## License
|
|
66
|
+
|
|
67
|
+
MIT — BridgeNode (https://bridgenode.cc)
|
|
@@ -0,0 +1,42 @@
|
|
|
1
|
+
# bridgenode
|
|
2
|
+
|
|
3
|
+
Official Python SDK for **BridgeNode** — automated x402 payment auth for AI agents.
|
|
4
|
+
|
|
5
|
+
`pip install bridgenode`
|
|
6
|
+
|
|
7
|
+
## Quick start
|
|
8
|
+
|
|
9
|
+
```python
|
|
10
|
+
from bridgenode import X402Client
|
|
11
|
+
|
|
12
|
+
client = X402Client()
|
|
13
|
+
print(f"My address: {client.address}")
|
|
14
|
+
|
|
15
|
+
response = await client.request(messages=[
|
|
16
|
+
{"role": "user", "content": "Hello, who are you?"}
|
|
17
|
+
])
|
|
18
|
+
print(response["choices"][0]["message"]["content"])
|
|
19
|
+
```
|
|
20
|
+
|
|
21
|
+
## How it works
|
|
22
|
+
|
|
23
|
+
1. Generates an ed25519 keypair (if none provided)
|
|
24
|
+
2. Signs each request with a UUID nonce
|
|
25
|
+
3. Sends X-Address / X-Signature / X-Nonce headers
|
|
26
|
+
4. Handles 402, 401, 409 responses automatically
|
|
27
|
+
|
|
28
|
+
## Install from source
|
|
29
|
+
|
|
30
|
+
```bash
|
|
31
|
+
cd bridgenode
|
|
32
|
+
pip install -e .
|
|
33
|
+
```
|
|
34
|
+
|
|
35
|
+
## Requirements
|
|
36
|
+
|
|
37
|
+
- Python ≥ 3.10
|
|
38
|
+
- httpx, pynacl, base58
|
|
39
|
+
|
|
40
|
+
## License
|
|
41
|
+
|
|
42
|
+
MIT — BridgeNode (https://bridgenode.cc)
|
|
@@ -0,0 +1,10 @@
|
|
|
1
|
+
"""x402-client — Python SDK for x402 Payment Required protocol.
|
|
2
|
+
|
|
3
|
+
Automatically handles HTTP 402 responses: detects payment challenges,
|
|
4
|
+
generates ed25519 keys, signs nonces, and retries with proper auth headers.
|
|
5
|
+
"""
|
|
6
|
+
|
|
7
|
+
from .client import X402Client
|
|
8
|
+
|
|
9
|
+
__version__ = "0.1.0"
|
|
10
|
+
__all__ = ["X402Client"]
|
|
@@ -0,0 +1,61 @@
|
|
|
1
|
+
"""Ed25519 key generation and signing for x402 authentication."""
|
|
2
|
+
|
|
3
|
+
import uuid
|
|
4
|
+
from typing import Tuple
|
|
5
|
+
|
|
6
|
+
import base58
|
|
7
|
+
import nacl.encoding
|
|
8
|
+
import nacl.signing
|
|
9
|
+
|
|
10
|
+
|
|
11
|
+
def generate_keypair() -> Tuple[str, str]:
|
|
12
|
+
"""Generate a new ed25519 keypair.
|
|
13
|
+
|
|
14
|
+
Returns:
|
|
15
|
+
Tuple of (private_key_base58, public_key_base58)
|
|
16
|
+
"""
|
|
17
|
+
sk = nacl.signing.SigningKey.generate()
|
|
18
|
+
private_key = base58.b58encode(sk.encode() + sk.verify_key.encode()).decode()
|
|
19
|
+
public_key = base58.b58encode(sk.verify_key.encode()).decode()
|
|
20
|
+
return private_key, public_key
|
|
21
|
+
|
|
22
|
+
|
|
23
|
+
def sign_nonce(nonce: str, private_key_base58: str) -> str:
|
|
24
|
+
"""Sign a nonce with the given ed25519 private key.
|
|
25
|
+
|
|
26
|
+
Args:
|
|
27
|
+
nonce: The nonce string to sign (usually a UUID).
|
|
28
|
+
private_key_base58: Base58-encoded ed25519 private key.
|
|
29
|
+
|
|
30
|
+
Returns:
|
|
31
|
+
Base58-encoded 64-byte ed25519 signature.
|
|
32
|
+
"""
|
|
33
|
+
priv_bytes = base58.b58decode(private_key_base58)
|
|
34
|
+
seed = priv_bytes[:32] if len(priv_bytes) > 32 else priv_bytes
|
|
35
|
+
sk = nacl.signing.SigningKey(seed)
|
|
36
|
+
signed = sk.sign(nonce.encode("utf-8"))
|
|
37
|
+
return base58.b58encode(signed.signature).decode()
|
|
38
|
+
|
|
39
|
+
|
|
40
|
+
def generate_nonce() -> str:
|
|
41
|
+
"""Generate a UUID v4 nonce for x402 authentication.
|
|
42
|
+
|
|
43
|
+
Returns:
|
|
44
|
+
UUID string (e.g., "123e4567-e89b-12d3-a456-426614174000")
|
|
45
|
+
"""
|
|
46
|
+
return str(uuid.uuid4())
|
|
47
|
+
|
|
48
|
+
|
|
49
|
+
def get_public_key(private_key_base58: str) -> str:
|
|
50
|
+
"""Derive public key from private key.
|
|
51
|
+
|
|
52
|
+
Args:
|
|
53
|
+
private_key_base58: Base58-encoded ed25519 private key.
|
|
54
|
+
|
|
55
|
+
Returns:
|
|
56
|
+
Base58-encoded public key.
|
|
57
|
+
"""
|
|
58
|
+
priv_bytes = base58.b58decode(private_key_base58)
|
|
59
|
+
seed = priv_bytes[:32] if len(priv_bytes) > 32 else priv_bytes
|
|
60
|
+
sk = nacl.signing.SigningKey(seed)
|
|
61
|
+
return base58.b58encode(sk.verify_key.encode()).decode()
|
|
@@ -0,0 +1,245 @@
|
|
|
1
|
+
"""X402Client — automated x402 payment handler for BridgeNode and compatible endpoints.
|
|
2
|
+
|
|
3
|
+
Usage:
|
|
4
|
+
client = X402Client(private_key="...")
|
|
5
|
+
response = client.request("https://bridgenode.cc/v1/inference", {
|
|
6
|
+
"messages": [{"role": "user", "content": "Hello"}]
|
|
7
|
+
})
|
|
8
|
+
print(response["choices"][0]["message"]["content"])
|
|
9
|
+
"""
|
|
10
|
+
|
|
11
|
+
import base64
|
|
12
|
+
import json
|
|
13
|
+
import logging
|
|
14
|
+
from typing import Any, Dict, Optional
|
|
15
|
+
|
|
16
|
+
import httpx
|
|
17
|
+
|
|
18
|
+
from .auth import generate_keypair, generate_nonce, get_public_key, sign_nonce
|
|
19
|
+
from .exceptions import X402AuthError, X402Error, X402InsufficientBalance, X402PaymentRequired
|
|
20
|
+
|
|
21
|
+
logger = logging.getLogger("bridgenode")
|
|
22
|
+
|
|
23
|
+
# Default BridgeNode URL
|
|
24
|
+
BRIDGENODE_URL = "https://bridgenode.cc/v1/inference"
|
|
25
|
+
# Default payment address (BridgeNode main wallet)
|
|
26
|
+
BRIDGENODE_PAYMENT_ADDRESS = "BHMDv3ri3LBEZjEzJgDZeUiguVX7LmsCstTXbM3dL8rN"
|
|
27
|
+
|
|
28
|
+
|
|
29
|
+
class X402Client:
|
|
30
|
+
"""HTTP client that handles 402 Payment Required automatically.
|
|
31
|
+
|
|
32
|
+
On receiving a 402 response:
|
|
33
|
+
1. Parses the PAYMENT-REQUIRED header for payment terms
|
|
34
|
+
2. Generates a UUID nonce and signs it with ed25519
|
|
35
|
+
3. Retries the request with X-Address, X-Signature, X-Nonce headers
|
|
36
|
+
|
|
37
|
+
If no private_key is provided, a new keypair is generated automatically.
|
|
38
|
+
"""
|
|
39
|
+
|
|
40
|
+
def __init__(
|
|
41
|
+
self,
|
|
42
|
+
private_key: Optional[str] = None,
|
|
43
|
+
http_client: Optional[httpx.AsyncClient] = None,
|
|
44
|
+
):
|
|
45
|
+
"""Initialize the x402 client.
|
|
46
|
+
|
|
47
|
+
Args:
|
|
48
|
+
private_key: Base58-encoded ed25519 private key. If None, generates a new one.
|
|
49
|
+
http_client: Optional httpx.AsyncClient for custom configuration.
|
|
50
|
+
A new client is created if not provided.
|
|
51
|
+
"""
|
|
52
|
+
if private_key:
|
|
53
|
+
self._private_key = private_key
|
|
54
|
+
self._public_key = get_public_key(private_key)
|
|
55
|
+
else:
|
|
56
|
+
self._private_key, self._public_key = generate_keypair()
|
|
57
|
+
logger.info(
|
|
58
|
+
"Generated new keypair. Address: %s\n"
|
|
59
|
+
"Keep this private key safe for future sessions:\n%s",
|
|
60
|
+
self._public_key, self._private_key,
|
|
61
|
+
)
|
|
62
|
+
|
|
63
|
+
self._http = http_client or httpx.AsyncClient(timeout=120.0)
|
|
64
|
+
|
|
65
|
+
@property
|
|
66
|
+
def address(self) -> str:
|
|
67
|
+
"""Solana wallet address (ed25519 public key, base58)."""
|
|
68
|
+
return self._public_key
|
|
69
|
+
|
|
70
|
+
async def request(
|
|
71
|
+
self,
|
|
72
|
+
url: str = BRIDGENODE_URL,
|
|
73
|
+
messages: Optional[list] = None,
|
|
74
|
+
max_tokens: int = 256,
|
|
75
|
+
stream: bool = False,
|
|
76
|
+
**kwargs,
|
|
77
|
+
) -> Dict[str, Any]:
|
|
78
|
+
"""Send a request with automatic x402 payment handling.
|
|
79
|
+
|
|
80
|
+
First tries with auth headers. If server returns 402,
|
|
81
|
+
generates nonce, signs it, and retries.
|
|
82
|
+
|
|
83
|
+
Args:
|
|
84
|
+
url: The x402-protected endpoint URL.
|
|
85
|
+
messages: List of message dicts [{"role": "...", "content": "..."}].
|
|
86
|
+
max_tokens: Maximum output tokens (default 256).
|
|
87
|
+
stream: Enable SSE streaming (default False).
|
|
88
|
+
**kwargs: Additional JSON body fields.
|
|
89
|
+
|
|
90
|
+
Returns:
|
|
91
|
+
Response dict with choices and response_metadata.
|
|
92
|
+
|
|
93
|
+
Raises:
|
|
94
|
+
X402PaymentRequired: Server requires payment and client can't pay.
|
|
95
|
+
X402AuthError: Invalid signature or auth failure.
|
|
96
|
+
X402InsufficientBalance: Agent has insufficient balance.
|
|
97
|
+
X402Error: Other x402 protocol errors.
|
|
98
|
+
"""
|
|
99
|
+
body = {
|
|
100
|
+
"messages": messages or [{"role": "user", "content": "Hello"}],
|
|
101
|
+
"max_tokens": max_tokens,
|
|
102
|
+
"stream": stream,
|
|
103
|
+
**kwargs,
|
|
104
|
+
}
|
|
105
|
+
|
|
106
|
+
# Generate nonce and sign for first attempt
|
|
107
|
+
nonce = generate_nonce()
|
|
108
|
+
signature = sign_nonce(nonce, self._private_key)
|
|
109
|
+
|
|
110
|
+
headers = {
|
|
111
|
+
"X-Address": self._public_key,
|
|
112
|
+
"X-Signature": signature,
|
|
113
|
+
"X-Nonce": nonce,
|
|
114
|
+
"Content-Type": "application/json",
|
|
115
|
+
}
|
|
116
|
+
|
|
117
|
+
logger.debug(
|
|
118
|
+
"Sending request to %s (address: %s...)", url, self._public_key[:12]
|
|
119
|
+
)
|
|
120
|
+
|
|
121
|
+
resp = await self._http.post(url, json=body, headers=headers)
|
|
122
|
+
|
|
123
|
+
if resp.status_code == 200:
|
|
124
|
+
return resp.json()
|
|
125
|
+
|
|
126
|
+
if resp.status_code == 402:
|
|
127
|
+
return await self._handle_402(resp, url, body)
|
|
128
|
+
|
|
129
|
+
if resp.status_code == 401:
|
|
130
|
+
raise X402AuthError(f"Invalid signature: {resp.text[:200]}")
|
|
131
|
+
|
|
132
|
+
if resp.status_code == 409:
|
|
133
|
+
# Nonce collision — retry with new nonce
|
|
134
|
+
logger.warning("Nonce conflict, retrying with new nonce")
|
|
135
|
+
new_nonce = generate_nonce()
|
|
136
|
+
new_sig = sign_nonce(new_nonce, self._private_key)
|
|
137
|
+
headers["X-Nonce"] = new_nonce
|
|
138
|
+
headers["X-Signature"] = new_sig
|
|
139
|
+
resp = await self._http.post(url, json=body, headers=headers)
|
|
140
|
+
if resp.status_code == 200:
|
|
141
|
+
return resp.json()
|
|
142
|
+
raise X402Error(f"Nonce conflict on retry: {resp.status_code} {resp.text[:200]}")
|
|
143
|
+
|
|
144
|
+
raise X402Error(f"Unexpected HTTP {resp.status_code}: {resp.text[:500]}")
|
|
145
|
+
|
|
146
|
+
async def _handle_402(
|
|
147
|
+
self,
|
|
148
|
+
resp: httpx.Response,
|
|
149
|
+
url: str,
|
|
150
|
+
body: dict,
|
|
151
|
+
) -> Dict[str, Any]:
|
|
152
|
+
"""Handle a 402 Payment Required response.
|
|
153
|
+
|
|
154
|
+
Parses the PAYMENT-REQUIRED header, extracts payment terms,
|
|
155
|
+
and retries with a fresh nonce and signature.
|
|
156
|
+
|
|
157
|
+
If the 402 is due to insufficient balance, raises X402InsufficientBalance.
|
|
158
|
+
"""
|
|
159
|
+
payment_header = resp.headers.get("PAYMENT-REQUIRED", "")
|
|
160
|
+
|
|
161
|
+
if payment_header:
|
|
162
|
+
try:
|
|
163
|
+
payment_req = json.loads(base64.b64decode(payment_header).decode())
|
|
164
|
+
accepts = payment_req.get("accepts", [])
|
|
165
|
+
if accepts:
|
|
166
|
+
terms = accepts[0]
|
|
167
|
+
required_amount = terms.get("amount", "?")
|
|
168
|
+
pay_to = terms.get("payTo", "?")
|
|
169
|
+
network = terms.get("network", "?")
|
|
170
|
+
|
|
171
|
+
logger.info(
|
|
172
|
+
"Payment required: %s %s tokens to %s (network: %s)",
|
|
173
|
+
required_amount, terms.get("asset", "USDC"), pay_to, network,
|
|
174
|
+
)
|
|
175
|
+
|
|
176
|
+
# Check if this is an insufficient balance response
|
|
177
|
+
detail = resp.json() if resp.text else {}
|
|
178
|
+
if isinstance(detail, dict) and detail.get("error") == "Insufficient balance":
|
|
179
|
+
raise X402InsufficientBalance(
|
|
180
|
+
f"Insufficient balance. Required: ${detail.get('required', '?')}, "
|
|
181
|
+
f"Balance: ${detail.get('balance', '?')}, "
|
|
182
|
+
f"Pay to: {detail.get('payTo', pay_to)}"
|
|
183
|
+
)
|
|
184
|
+
except (json.JSONDecodeError, base64.binascii.Error, UnicodeDecodeError) as e:
|
|
185
|
+
logger.warning("Failed to parse PAYMENT-REQUIRED header: %s", e)
|
|
186
|
+
|
|
187
|
+
# Retry with fresh nonce (the previous nonce was never recorded)
|
|
188
|
+
new_nonce = generate_nonce()
|
|
189
|
+
new_sig = sign_nonce(new_nonce, self._private_key)
|
|
190
|
+
headers = {
|
|
191
|
+
"X-Address": self._public_key,
|
|
192
|
+
"X-Signature": new_sig,
|
|
193
|
+
"X-Nonce": new_nonce,
|
|
194
|
+
"Content-Type": "application/json",
|
|
195
|
+
}
|
|
196
|
+
|
|
197
|
+
retry_resp = await self._http.post(url, json=body, headers=headers)
|
|
198
|
+
|
|
199
|
+
if retry_resp.status_code == 200:
|
|
200
|
+
return retry_resp.json()
|
|
201
|
+
|
|
202
|
+
# Still failing — check again for insufficient balance
|
|
203
|
+
if retry_resp.status_code == 402:
|
|
204
|
+
detail = retry_resp.json() if retry_resp.text else {}
|
|
205
|
+
if isinstance(detail, dict) and isinstance(detail.get("detail"), dict):
|
|
206
|
+
err = detail["detail"]
|
|
207
|
+
if err.get("error") == "Insufficient balance":
|
|
208
|
+
raise X402InsufficientBalance(
|
|
209
|
+
f"Insufficient balance. Required: ${err.get('required', '?')}, "
|
|
210
|
+
f"Balance: ${err.get('balance', '?')}, "
|
|
211
|
+
f"Pay to: {err.get('payTo', BRIDGENODE_PAYMENT_ADDRESS)}"
|
|
212
|
+
)
|
|
213
|
+
|
|
214
|
+
raise X402PaymentRequired(
|
|
215
|
+
f"Payment required. "
|
|
216
|
+
f"Deposit USDC to {BRIDGENODE_PAYMENT_ADDRESS} (Solana mainnet). "
|
|
217
|
+
f"Check balance at https://bridgenode.cc/v1/balance/{self._public_key}"
|
|
218
|
+
)
|
|
219
|
+
|
|
220
|
+
if retry_resp.status_code == 401:
|
|
221
|
+
raise X402AuthError(f"Signature rejected on retry: {retry_resp.text[:200]}")
|
|
222
|
+
|
|
223
|
+
raise X402Error(
|
|
224
|
+
f"Payment retry failed (HTTP {retry_resp.status_code}): {retry_resp.text[:500]}"
|
|
225
|
+
)
|
|
226
|
+
|
|
227
|
+
async def check_balance(self, address: Optional[str] = None) -> float:
|
|
228
|
+
"""Check off-chain USDC balance on BridgeNode.
|
|
229
|
+
|
|
230
|
+
Args:
|
|
231
|
+
address: Solana wallet address. Uses client address if not provided.
|
|
232
|
+
|
|
233
|
+
Returns:
|
|
234
|
+
Balance in USDC.
|
|
235
|
+
"""
|
|
236
|
+
addr = address or self._public_key
|
|
237
|
+
resp = await self._http.get(f"https://bridgenode.cc/v1/balance/{addr}")
|
|
238
|
+
if resp.status_code == 200:
|
|
239
|
+
data = resp.json()
|
|
240
|
+
return float(data.get("balance", 0.0))
|
|
241
|
+
raise X402Error(f"Balance check failed: HTTP {resp.status_code}")
|
|
242
|
+
|
|
243
|
+
async def close(self):
|
|
244
|
+
"""Close the underlying HTTP client."""
|
|
245
|
+
await self._http.aclose()
|
|
@@ -0,0 +1,20 @@
|
|
|
1
|
+
"""Custom exceptions for x402-client."""
|
|
2
|
+
|
|
3
|
+
|
|
4
|
+
class X402Error(Exception):
|
|
5
|
+
"""Base exception for all x402-client errors."""
|
|
6
|
+
|
|
7
|
+
|
|
8
|
+
class X402PaymentRequired(X402Error):
|
|
9
|
+
"""Server returned 402 Payment Required and client could not pay.
|
|
10
|
+
|
|
11
|
+
This happens when no private key is configured or payment failed.
|
|
12
|
+
"""
|
|
13
|
+
|
|
14
|
+
|
|
15
|
+
class X402AuthError(X402Error):
|
|
16
|
+
"""Authentication error - invalid signature or address mismatch."""
|
|
17
|
+
|
|
18
|
+
|
|
19
|
+
class X402InsufficientBalance(X402Error):
|
|
20
|
+
"""Server returned 402 with insufficient balance."""
|
|
@@ -0,0 +1,67 @@
|
|
|
1
|
+
Metadata-Version: 2.4
|
|
2
|
+
Name: bridgenode
|
|
3
|
+
Version: 0.1.0
|
|
4
|
+
Summary: Official Python SDK for BridgeNode — automated x402 payment auth for AI agents
|
|
5
|
+
Author-email: BridgeNode <eli.BNx@proton.me>
|
|
6
|
+
License: MIT
|
|
7
|
+
Project-URL: Homepage, https://bridgenode.cc
|
|
8
|
+
Project-URL: Source, https://github.com/BridgeNode/x402-client
|
|
9
|
+
Project-URL: Tracker, https://github.com/BridgeNode/x402-client/issues
|
|
10
|
+
Keywords: x402,payment,solana,ed25519,ai,agent
|
|
11
|
+
Classifier: Development Status :: 4 - Beta
|
|
12
|
+
Classifier: Intended Audience :: Developers
|
|
13
|
+
Classifier: License :: OSI Approved :: MIT License
|
|
14
|
+
Classifier: Programming Language :: Python :: 3.10
|
|
15
|
+
Classifier: Programming Language :: Python :: 3.11
|
|
16
|
+
Classifier: Programming Language :: Python :: 3.12
|
|
17
|
+
Classifier: Programming Language :: Python :: 3.13
|
|
18
|
+
Classifier: Topic :: Internet :: WWW/HTTP
|
|
19
|
+
Classifier: Topic :: Security :: Cryptography
|
|
20
|
+
Requires-Python: >=3.10
|
|
21
|
+
Description-Content-Type: text/markdown
|
|
22
|
+
Requires-Dist: httpx>=0.28.0
|
|
23
|
+
Requires-Dist: pynacl>=1.6.0
|
|
24
|
+
Requires-Dist: base58>=2.1.0
|
|
25
|
+
|
|
26
|
+
# bridgenode
|
|
27
|
+
|
|
28
|
+
Official Python SDK for **BridgeNode** — automated x402 payment auth for AI agents.
|
|
29
|
+
|
|
30
|
+
`pip install bridgenode`
|
|
31
|
+
|
|
32
|
+
## Quick start
|
|
33
|
+
|
|
34
|
+
```python
|
|
35
|
+
from bridgenode import X402Client
|
|
36
|
+
|
|
37
|
+
client = X402Client()
|
|
38
|
+
print(f"My address: {client.address}")
|
|
39
|
+
|
|
40
|
+
response = await client.request(messages=[
|
|
41
|
+
{"role": "user", "content": "Hello, who are you?"}
|
|
42
|
+
])
|
|
43
|
+
print(response["choices"][0]["message"]["content"])
|
|
44
|
+
```
|
|
45
|
+
|
|
46
|
+
## How it works
|
|
47
|
+
|
|
48
|
+
1. Generates an ed25519 keypair (if none provided)
|
|
49
|
+
2. Signs each request with a UUID nonce
|
|
50
|
+
3. Sends X-Address / X-Signature / X-Nonce headers
|
|
51
|
+
4. Handles 402, 401, 409 responses automatically
|
|
52
|
+
|
|
53
|
+
## Install from source
|
|
54
|
+
|
|
55
|
+
```bash
|
|
56
|
+
cd bridgenode
|
|
57
|
+
pip install -e .
|
|
58
|
+
```
|
|
59
|
+
|
|
60
|
+
## Requirements
|
|
61
|
+
|
|
62
|
+
- Python ≥ 3.10
|
|
63
|
+
- httpx, pynacl, base58
|
|
64
|
+
|
|
65
|
+
## License
|
|
66
|
+
|
|
67
|
+
MIT — BridgeNode (https://bridgenode.cc)
|
|
@@ -0,0 +1,12 @@
|
|
|
1
|
+
README.md
|
|
2
|
+
pyproject.toml
|
|
3
|
+
bridgenode/__init__.py
|
|
4
|
+
bridgenode/auth.py
|
|
5
|
+
bridgenode/client.py
|
|
6
|
+
bridgenode/exceptions.py
|
|
7
|
+
bridgenode.egg-info/PKG-INFO
|
|
8
|
+
bridgenode.egg-info/SOURCES.txt
|
|
9
|
+
bridgenode.egg-info/dependency_links.txt
|
|
10
|
+
bridgenode.egg-info/entry_points.txt
|
|
11
|
+
bridgenode.egg-info/requires.txt
|
|
12
|
+
bridgenode.egg-info/top_level.txt
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
bridgenode
|
|
@@ -0,0 +1,39 @@
|
|
|
1
|
+
[build-system]
|
|
2
|
+
requires = ["setuptools>=64", "wheel"]
|
|
3
|
+
build-backend = "setuptools.build_meta"
|
|
4
|
+
|
|
5
|
+
[project]
|
|
6
|
+
name = "bridgenode"
|
|
7
|
+
version = "0.1.0"
|
|
8
|
+
description = "Official Python SDK for BridgeNode — automated x402 payment auth for AI agents"
|
|
9
|
+
authors = [
|
|
10
|
+
{name = "BridgeNode", email = "eli.BNx@proton.me"},
|
|
11
|
+
]
|
|
12
|
+
readme = "README.md"
|
|
13
|
+
license = {text = "MIT"}
|
|
14
|
+
requires-python = ">=3.10"
|
|
15
|
+
keywords = ["x402", "payment", "solana", "ed25519", "ai", "agent"]
|
|
16
|
+
classifiers = [
|
|
17
|
+
"Development Status :: 4 - Beta",
|
|
18
|
+
"Intended Audience :: Developers",
|
|
19
|
+
"License :: OSI Approved :: MIT License",
|
|
20
|
+
"Programming Language :: Python :: 3.10",
|
|
21
|
+
"Programming Language :: Python :: 3.11",
|
|
22
|
+
"Programming Language :: Python :: 3.12",
|
|
23
|
+
"Programming Language :: Python :: 3.13",
|
|
24
|
+
"Topic :: Internet :: WWW/HTTP",
|
|
25
|
+
"Topic :: Security :: Cryptography",
|
|
26
|
+
]
|
|
27
|
+
dependencies = [
|
|
28
|
+
"httpx>=0.28.0",
|
|
29
|
+
"pynacl>=1.6.0",
|
|
30
|
+
"base58>=2.1.0",
|
|
31
|
+
]
|
|
32
|
+
|
|
33
|
+
[project.urls]
|
|
34
|
+
Homepage = "https://bridgenode.cc"
|
|
35
|
+
Source = "https://github.com/BridgeNode/x402-client"
|
|
36
|
+
Tracker = "https://github.com/BridgeNode/x402-client/issues"
|
|
37
|
+
|
|
38
|
+
[project.scripts]
|
|
39
|
+
x402-demo = "bridgenode.__main__:main"
|