smart402 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.
- smart402-0.1.0/LICENSE +13 -0
- smart402-0.1.0/PKG-INFO +239 -0
- smart402-0.1.0/README.md +204 -0
- smart402-0.1.0/pyproject.toml +49 -0
- smart402-0.1.0/setup.cfg +4 -0
- smart402-0.1.0/smart402/__init__.py +12 -0
- smart402-0.1.0/smart402/client.py +91 -0
- smart402-0.1.0/smart402/guard.py +266 -0
- smart402-0.1.0/smart402/models.py +57 -0
- smart402-0.1.0/smart402.egg-info/PKG-INFO +239 -0
- smart402-0.1.0/smart402.egg-info/SOURCES.txt +14 -0
- smart402-0.1.0/smart402.egg-info/dependency_links.txt +1 -0
- smart402-0.1.0/smart402.egg-info/requires.txt +13 -0
- smart402-0.1.0/smart402.egg-info/top_level.txt +1 -0
- smart402-0.1.0/tests/test_client.py +239 -0
- smart402-0.1.0/tests/test_guard.py +232 -0
smart402-0.1.0/LICENSE
ADDED
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
Copyright 2026 Roberto Moreno
|
|
2
|
+
|
|
3
|
+
Licensed under the Apache License, Version 2.0 (the "License");
|
|
4
|
+
you may not use this file except in compliance with the License.
|
|
5
|
+
You may obtain a copy of the License at
|
|
6
|
+
|
|
7
|
+
http://www.apache.org/licenses/LICENSE-2.0
|
|
8
|
+
|
|
9
|
+
Unless required by applicable law or agreed to in writing, software
|
|
10
|
+
distributed under the License is distributed on an "AS IS" BASIS,
|
|
11
|
+
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
|
12
|
+
See the License for the specific language governing permissions and
|
|
13
|
+
limitations under the License.
|
smart402-0.1.0/PKG-INFO
ADDED
|
@@ -0,0 +1,239 @@
|
|
|
1
|
+
Metadata-Version: 2.4
|
|
2
|
+
Name: smart402
|
|
3
|
+
Version: 0.1.0
|
|
4
|
+
Summary: Deterministic risk engine for AI agent payments via x402
|
|
5
|
+
Author-email: Roberto Moreno <rob@smart402.com>
|
|
6
|
+
License: Apache-2.0
|
|
7
|
+
Project-URL: Homepage, https://github.com/Falinkaz/smart402-python
|
|
8
|
+
Project-URL: Documentation, https://github.com/Falinkaz/smart402-python/blob/main/API.md
|
|
9
|
+
Project-URL: Repository, https://github.com/Falinkaz/smart402-python
|
|
10
|
+
Project-URL: Issues, https://github.com/Falinkaz/smart402-python/issues
|
|
11
|
+
Keywords: x402,ai-agents,payments,risk,stablecoin,blockchain
|
|
12
|
+
Classifier: Development Status :: 4 - Beta
|
|
13
|
+
Classifier: Intended Audience :: Developers
|
|
14
|
+
Classifier: License :: OSI Approved :: Apache Software License
|
|
15
|
+
Classifier: Programming Language :: Python :: 3
|
|
16
|
+
Classifier: Programming Language :: Python :: 3.10
|
|
17
|
+
Classifier: Programming Language :: Python :: 3.11
|
|
18
|
+
Classifier: Programming Language :: Python :: 3.12
|
|
19
|
+
Classifier: Programming Language :: Python :: 3.13
|
|
20
|
+
Classifier: Topic :: Software Development :: Libraries
|
|
21
|
+
Requires-Python: >=3.10
|
|
22
|
+
Description-Content-Type: text/markdown
|
|
23
|
+
License-File: LICENSE
|
|
24
|
+
Requires-Dist: httpx>=0.25.0
|
|
25
|
+
Requires-Dist: pydantic>=2.0
|
|
26
|
+
Provides-Extra: x402
|
|
27
|
+
Requires-Dist: x402[evm,httpx]<3,>=2.3.0; extra == "x402"
|
|
28
|
+
Provides-Extra: all
|
|
29
|
+
Requires-Dist: x402[evm,httpx]<3,>=2.3.0; extra == "all"
|
|
30
|
+
Provides-Extra: dev
|
|
31
|
+
Requires-Dist: pytest>=8.0; extra == "dev"
|
|
32
|
+
Requires-Dist: pytest-asyncio>=0.23; extra == "dev"
|
|
33
|
+
Requires-Dist: pytest-httpx>=0.30; extra == "dev"
|
|
34
|
+
Dynamic: license-file
|
|
35
|
+
|
|
36
|
+
# smart402 — Python SDK
|
|
37
|
+
|
|
38
|
+
Deterministic policy engine for AI agent payments via [x402](https://x402.org).
|
|
39
|
+
|
|
40
|
+
No LLM in the decision path. Every approve/deny traces to a rule your team configured — not a model's judgment call. A compromised agent cannot reason or prompt-inject its way past smart402.
|
|
41
|
+
|
|
42
|
+
> **v0.1:** smart402 currently supports USDC transactions on Base (eip155:8453). Other tokens and chains are on the roadmap.
|
|
43
|
+
|
|
44
|
+
## Install
|
|
45
|
+
|
|
46
|
+
```bash
|
|
47
|
+
pip install smart402
|
|
48
|
+
```
|
|
49
|
+
|
|
50
|
+
For x402 integration extras:
|
|
51
|
+
|
|
52
|
+
```bash
|
|
53
|
+
pip install "smart402[x402]"
|
|
54
|
+
```
|
|
55
|
+
|
|
56
|
+
Python 3.10+ required.
|
|
57
|
+
|
|
58
|
+
## Before you start
|
|
59
|
+
|
|
60
|
+
1. Sign up at https://smart402-dashboard.vercel.app
|
|
61
|
+
2. Create an agent in the dashboard
|
|
62
|
+
3. Configure at least one policy (e.g., daily budget of $10)
|
|
63
|
+
4. Create an evaluate-scoped API key in Settings → API Keys
|
|
64
|
+
5. Follow the Quick Start below.
|
|
65
|
+
|
|
66
|
+
## Quick Start
|
|
67
|
+
|
|
68
|
+
```python
|
|
69
|
+
import asyncio
|
|
70
|
+
import os
|
|
71
|
+
from smart402 import Smart402Client
|
|
72
|
+
|
|
73
|
+
async def main():
|
|
74
|
+
client = Smart402Client(
|
|
75
|
+
api_key=os.environ["SMART402_AGENT_KEY"],
|
|
76
|
+
agent_id="my-agent-001",
|
|
77
|
+
)
|
|
78
|
+
|
|
79
|
+
result = await client.evaluate_payment(
|
|
80
|
+
amount="0.10", # USDC amount as a decimal string. "0.10" = ten cents. The Python SDK expects pre-converted dollar decimals.
|
|
81
|
+
token="USDC",
|
|
82
|
+
network="eip155:8453", # Base mainnet (CAIP-2)
|
|
83
|
+
pay_to="0x9dBA414637c611a16BEa6f0796BFcbcBdc410df8",
|
|
84
|
+
)
|
|
85
|
+
|
|
86
|
+
print(result.decision) # "approve" or "deny"
|
|
87
|
+
print(result.triggered_rules) # [] or ["counterparty_not_on_allowlist", ...]
|
|
88
|
+
|
|
89
|
+
asyncio.run(main())
|
|
90
|
+
```
|
|
91
|
+
|
|
92
|
+
[Get an API key →](https://smart402-dashboard.vercel.app)
|
|
93
|
+
|
|
94
|
+
**Synchronous usage:** `asyncio.run()` wraps any async call. A sync API is planned for v0.2.
|
|
95
|
+
|
|
96
|
+
## x402 Integration
|
|
97
|
+
|
|
98
|
+
If your agent uses the [x402 Python SDK](https://github.com/coinbase/x402), register smart402 as a lifecycle hook:
|
|
99
|
+
|
|
100
|
+
```python
|
|
101
|
+
from smart402 import smart402_hook
|
|
102
|
+
from x402 import x402Client
|
|
103
|
+
from x402.mechanisms.evm.exact import register_exact_evm_client
|
|
104
|
+
from x402.mechanisms.evm.signers import EthAccountSigner
|
|
105
|
+
from eth_account import Account
|
|
106
|
+
|
|
107
|
+
account = Account.from_key(os.environ["EVM_PRIVATE_KEY"])
|
|
108
|
+
signer = EthAccountSigner(account)
|
|
109
|
+
|
|
110
|
+
client = x402Client()
|
|
111
|
+
register_exact_evm_client(client, signer)
|
|
112
|
+
|
|
113
|
+
# One line to add smart402 protection
|
|
114
|
+
client.on_before_payment_creation(
|
|
115
|
+
smart402_hook(
|
|
116
|
+
api_key=os.environ["SMART402_AGENT_KEY"],
|
|
117
|
+
agent_id="my-agent-001",
|
|
118
|
+
agent_wallet_address=signer.address,
|
|
119
|
+
)
|
|
120
|
+
)
|
|
121
|
+
|
|
122
|
+
# Every payment the x402 client makes is now evaluated first
|
|
123
|
+
```
|
|
124
|
+
|
|
125
|
+
The hook fires before each payment is signed. If smart402 denies the payment, `AbortResult` is returned and the payment is not made.
|
|
126
|
+
|
|
127
|
+
## Advanced Usage
|
|
128
|
+
|
|
129
|
+
For full control over all request fields, use the Pydantic models directly:
|
|
130
|
+
|
|
131
|
+
```python
|
|
132
|
+
from smart402 import Smart402Client
|
|
133
|
+
from smart402.models import EvaluateRequest, PaymentRequirementsPayload
|
|
134
|
+
|
|
135
|
+
client = Smart402Client(
|
|
136
|
+
api_key=os.environ["SMART402_AGENT_KEY"],
|
|
137
|
+
agent_id="my-agent-001",
|
|
138
|
+
)
|
|
139
|
+
|
|
140
|
+
result = await client.evaluate(
|
|
141
|
+
EvaluateRequest(
|
|
142
|
+
agent_id=client.agent_id, # set in the constructor above
|
|
143
|
+
agent_wallet_address="0x...",
|
|
144
|
+
payment_requirements=PaymentRequirementsPayload(
|
|
145
|
+
amount="0.10",
|
|
146
|
+
token="USDC",
|
|
147
|
+
network="eip155:8453",
|
|
148
|
+
pay_to="0x9dBA414637c611a16BEa6f0796BFcbcBdc410df8",
|
|
149
|
+
),
|
|
150
|
+
)
|
|
151
|
+
)
|
|
152
|
+
```
|
|
153
|
+
|
|
154
|
+
## Configuration
|
|
155
|
+
|
|
156
|
+
**`Smart402Client(api_key, agent_id, ...)`**
|
|
157
|
+
|
|
158
|
+
| Parameter | Default | Description |
|
|
159
|
+
|-----------|---------|-------------|
|
|
160
|
+
| `api_key` | required | smart402 API key |
|
|
161
|
+
| `agent_id` | required | Agent identifier (from dashboard) |
|
|
162
|
+
| `base_url` | `https://streetsmart-api.fly.dev` | API base URL |
|
|
163
|
+
|
|
164
|
+
**Amount format:** Pass `amount` as a decimal dollar string — `"0.10"` = ten cents. The Python SDK expects pre-converted dollar decimals. If you're reading the amount from an x402 `PaymentRequirements` object (which uses raw USDC units), convert it first: `str(int(raw_amount) / 1_000_000)`.
|
|
165
|
+
|
|
166
|
+
**`smart402_hook(api_key, agent_id, ...)`**
|
|
167
|
+
|
|
168
|
+
| Parameter | Default | Description |
|
|
169
|
+
|-----------|---------|-------------|
|
|
170
|
+
| `api_key` | required | smart402 API key |
|
|
171
|
+
| `agent_id` | required | Agent identifier (from dashboard) |
|
|
172
|
+
| `smart402_url` | `https://streetsmart-api.fly.dev` | API base URL |
|
|
173
|
+
| `fail_mode` | `"fail_open"` | Behavior when API is unreachable |
|
|
174
|
+
| `agent_wallet_address` | `None` | Agent's public EVM address |
|
|
175
|
+
| `wallet_provider` | `None` | e.g. `"coinbase"`, `"local_evm"` |
|
|
176
|
+
| `agent_framework` | `None` | e.g. `"langchain"`, `"langgraph"` |
|
|
177
|
+
|
|
178
|
+
## Fail-Open vs Fail-Closed
|
|
179
|
+
|
|
180
|
+
```python
|
|
181
|
+
# fail_open (default): if smart402 is unreachable, payment proceeds
|
|
182
|
+
smart402_hook(api_key="...", agent_id="...", fail_mode="fail_open")
|
|
183
|
+
|
|
184
|
+
# fail_closed: if smart402 is unreachable, payment is blocked
|
|
185
|
+
smart402_hook(api_key="...", agent_id="...", fail_mode="fail_closed")
|
|
186
|
+
```
|
|
187
|
+
|
|
188
|
+
| Mode | When API is unreachable |
|
|
189
|
+
|------|------------------------|
|
|
190
|
+
| `fail_open` (default) | Warning logged, payment proceeds |
|
|
191
|
+
| `fail_closed` | `AbortResult` returned to x402 — payment is not made |
|
|
192
|
+
|
|
193
|
+
## Error Handling
|
|
194
|
+
|
|
195
|
+
```python
|
|
196
|
+
result = await client.evaluate_payment(
|
|
197
|
+
amount="0.10", token="USDC",
|
|
198
|
+
network="eip155:8453", pay_to="0x...",
|
|
199
|
+
)
|
|
200
|
+
if result.decision == "deny":
|
|
201
|
+
print("Blocked by:", result.triggered_rules)
|
|
202
|
+
print("Evaluation ID:", result.evaluation_id)
|
|
203
|
+
```
|
|
204
|
+
|
|
205
|
+
When using `smart402_hook()`, a denied payment returns `AbortResult` to the x402 client — the payment is not made and no exception is raised to your code. When calling `Smart402Client.evaluate()` directly, check `result.decision` — the client always returns the response, never raises on denial.
|
|
206
|
+
|
|
207
|
+
`Smart402Denied` and `Smart402Unavailable` are not raised in v0.1.
|
|
208
|
+
|
|
209
|
+
## What data leaves your machine
|
|
210
|
+
|
|
211
|
+
The SDK sends to the smart402 API:
|
|
212
|
+
- amount, token, network, recipient address
|
|
213
|
+
- agent ID and wallet address (public, not private key)
|
|
214
|
+
|
|
215
|
+
The SDK never sends:
|
|
216
|
+
- Private keys, seed phrases, or wallet passwords
|
|
217
|
+
- Signed transactions or raw transaction data
|
|
218
|
+
- Wallet balances
|
|
219
|
+
|
|
220
|
+
One HTTPS call to `POST /evaluate`. No telemetry, no analytics, no side-channel requests.
|
|
221
|
+
Verify: the SDK is ~200 lines of code. Read it.
|
|
222
|
+
|
|
223
|
+
Read the full trust model: [SECURITY.md](https://github.com/Falinkaz/smart402-python/blob/main/SECURITY.md)
|
|
224
|
+
|
|
225
|
+
## Limits
|
|
226
|
+
|
|
227
|
+
- Rate limit: 600 requests per minute per account
|
|
228
|
+
- Typical latency: 10–50ms (p50), under 200ms (p99)
|
|
229
|
+
- If the API is unreachable, `fail_open` (default) lets the payment proceed. `fail_closed` blocks it.
|
|
230
|
+
- The SDK does not retry on failure — it returns the error immediately, keeping latency predictable and letting you own retry logic.
|
|
231
|
+
- Default request timeout: 5 seconds (not configurable in v0.1)
|
|
232
|
+
|
|
233
|
+
## API Reference
|
|
234
|
+
|
|
235
|
+
Full endpoint documentation: [API.md](https://github.com/Falinkaz/smart402-python/blob/main/API.md)
|
|
236
|
+
|
|
237
|
+
## License
|
|
238
|
+
|
|
239
|
+
Apache 2.0 — see [LICENSE](https://github.com/Falinkaz/smart402-python/blob/main/LICENSE)
|
smart402-0.1.0/README.md
ADDED
|
@@ -0,0 +1,204 @@
|
|
|
1
|
+
# smart402 — Python SDK
|
|
2
|
+
|
|
3
|
+
Deterministic policy engine for AI agent payments via [x402](https://x402.org).
|
|
4
|
+
|
|
5
|
+
No LLM in the decision path. Every approve/deny traces to a rule your team configured — not a model's judgment call. A compromised agent cannot reason or prompt-inject its way past smart402.
|
|
6
|
+
|
|
7
|
+
> **v0.1:** smart402 currently supports USDC transactions on Base (eip155:8453). Other tokens and chains are on the roadmap.
|
|
8
|
+
|
|
9
|
+
## Install
|
|
10
|
+
|
|
11
|
+
```bash
|
|
12
|
+
pip install smart402
|
|
13
|
+
```
|
|
14
|
+
|
|
15
|
+
For x402 integration extras:
|
|
16
|
+
|
|
17
|
+
```bash
|
|
18
|
+
pip install "smart402[x402]"
|
|
19
|
+
```
|
|
20
|
+
|
|
21
|
+
Python 3.10+ required.
|
|
22
|
+
|
|
23
|
+
## Before you start
|
|
24
|
+
|
|
25
|
+
1. Sign up at https://smart402-dashboard.vercel.app
|
|
26
|
+
2. Create an agent in the dashboard
|
|
27
|
+
3. Configure at least one policy (e.g., daily budget of $10)
|
|
28
|
+
4. Create an evaluate-scoped API key in Settings → API Keys
|
|
29
|
+
5. Follow the Quick Start below.
|
|
30
|
+
|
|
31
|
+
## Quick Start
|
|
32
|
+
|
|
33
|
+
```python
|
|
34
|
+
import asyncio
|
|
35
|
+
import os
|
|
36
|
+
from smart402 import Smart402Client
|
|
37
|
+
|
|
38
|
+
async def main():
|
|
39
|
+
client = Smart402Client(
|
|
40
|
+
api_key=os.environ["SMART402_AGENT_KEY"],
|
|
41
|
+
agent_id="my-agent-001",
|
|
42
|
+
)
|
|
43
|
+
|
|
44
|
+
result = await client.evaluate_payment(
|
|
45
|
+
amount="0.10", # USDC amount as a decimal string. "0.10" = ten cents. The Python SDK expects pre-converted dollar decimals.
|
|
46
|
+
token="USDC",
|
|
47
|
+
network="eip155:8453", # Base mainnet (CAIP-2)
|
|
48
|
+
pay_to="0x9dBA414637c611a16BEa6f0796BFcbcBdc410df8",
|
|
49
|
+
)
|
|
50
|
+
|
|
51
|
+
print(result.decision) # "approve" or "deny"
|
|
52
|
+
print(result.triggered_rules) # [] or ["counterparty_not_on_allowlist", ...]
|
|
53
|
+
|
|
54
|
+
asyncio.run(main())
|
|
55
|
+
```
|
|
56
|
+
|
|
57
|
+
[Get an API key →](https://smart402-dashboard.vercel.app)
|
|
58
|
+
|
|
59
|
+
**Synchronous usage:** `asyncio.run()` wraps any async call. A sync API is planned for v0.2.
|
|
60
|
+
|
|
61
|
+
## x402 Integration
|
|
62
|
+
|
|
63
|
+
If your agent uses the [x402 Python SDK](https://github.com/coinbase/x402), register smart402 as a lifecycle hook:
|
|
64
|
+
|
|
65
|
+
```python
|
|
66
|
+
from smart402 import smart402_hook
|
|
67
|
+
from x402 import x402Client
|
|
68
|
+
from x402.mechanisms.evm.exact import register_exact_evm_client
|
|
69
|
+
from x402.mechanisms.evm.signers import EthAccountSigner
|
|
70
|
+
from eth_account import Account
|
|
71
|
+
|
|
72
|
+
account = Account.from_key(os.environ["EVM_PRIVATE_KEY"])
|
|
73
|
+
signer = EthAccountSigner(account)
|
|
74
|
+
|
|
75
|
+
client = x402Client()
|
|
76
|
+
register_exact_evm_client(client, signer)
|
|
77
|
+
|
|
78
|
+
# One line to add smart402 protection
|
|
79
|
+
client.on_before_payment_creation(
|
|
80
|
+
smart402_hook(
|
|
81
|
+
api_key=os.environ["SMART402_AGENT_KEY"],
|
|
82
|
+
agent_id="my-agent-001",
|
|
83
|
+
agent_wallet_address=signer.address,
|
|
84
|
+
)
|
|
85
|
+
)
|
|
86
|
+
|
|
87
|
+
# Every payment the x402 client makes is now evaluated first
|
|
88
|
+
```
|
|
89
|
+
|
|
90
|
+
The hook fires before each payment is signed. If smart402 denies the payment, `AbortResult` is returned and the payment is not made.
|
|
91
|
+
|
|
92
|
+
## Advanced Usage
|
|
93
|
+
|
|
94
|
+
For full control over all request fields, use the Pydantic models directly:
|
|
95
|
+
|
|
96
|
+
```python
|
|
97
|
+
from smart402 import Smart402Client
|
|
98
|
+
from smart402.models import EvaluateRequest, PaymentRequirementsPayload
|
|
99
|
+
|
|
100
|
+
client = Smart402Client(
|
|
101
|
+
api_key=os.environ["SMART402_AGENT_KEY"],
|
|
102
|
+
agent_id="my-agent-001",
|
|
103
|
+
)
|
|
104
|
+
|
|
105
|
+
result = await client.evaluate(
|
|
106
|
+
EvaluateRequest(
|
|
107
|
+
agent_id=client.agent_id, # set in the constructor above
|
|
108
|
+
agent_wallet_address="0x...",
|
|
109
|
+
payment_requirements=PaymentRequirementsPayload(
|
|
110
|
+
amount="0.10",
|
|
111
|
+
token="USDC",
|
|
112
|
+
network="eip155:8453",
|
|
113
|
+
pay_to="0x9dBA414637c611a16BEa6f0796BFcbcBdc410df8",
|
|
114
|
+
),
|
|
115
|
+
)
|
|
116
|
+
)
|
|
117
|
+
```
|
|
118
|
+
|
|
119
|
+
## Configuration
|
|
120
|
+
|
|
121
|
+
**`Smart402Client(api_key, agent_id, ...)`**
|
|
122
|
+
|
|
123
|
+
| Parameter | Default | Description |
|
|
124
|
+
|-----------|---------|-------------|
|
|
125
|
+
| `api_key` | required | smart402 API key |
|
|
126
|
+
| `agent_id` | required | Agent identifier (from dashboard) |
|
|
127
|
+
| `base_url` | `https://streetsmart-api.fly.dev` | API base URL |
|
|
128
|
+
|
|
129
|
+
**Amount format:** Pass `amount` as a decimal dollar string — `"0.10"` = ten cents. The Python SDK expects pre-converted dollar decimals. If you're reading the amount from an x402 `PaymentRequirements` object (which uses raw USDC units), convert it first: `str(int(raw_amount) / 1_000_000)`.
|
|
130
|
+
|
|
131
|
+
**`smart402_hook(api_key, agent_id, ...)`**
|
|
132
|
+
|
|
133
|
+
| Parameter | Default | Description |
|
|
134
|
+
|-----------|---------|-------------|
|
|
135
|
+
| `api_key` | required | smart402 API key |
|
|
136
|
+
| `agent_id` | required | Agent identifier (from dashboard) |
|
|
137
|
+
| `smart402_url` | `https://streetsmart-api.fly.dev` | API base URL |
|
|
138
|
+
| `fail_mode` | `"fail_open"` | Behavior when API is unreachable |
|
|
139
|
+
| `agent_wallet_address` | `None` | Agent's public EVM address |
|
|
140
|
+
| `wallet_provider` | `None` | e.g. `"coinbase"`, `"local_evm"` |
|
|
141
|
+
| `agent_framework` | `None` | e.g. `"langchain"`, `"langgraph"` |
|
|
142
|
+
|
|
143
|
+
## Fail-Open vs Fail-Closed
|
|
144
|
+
|
|
145
|
+
```python
|
|
146
|
+
# fail_open (default): if smart402 is unreachable, payment proceeds
|
|
147
|
+
smart402_hook(api_key="...", agent_id="...", fail_mode="fail_open")
|
|
148
|
+
|
|
149
|
+
# fail_closed: if smart402 is unreachable, payment is blocked
|
|
150
|
+
smart402_hook(api_key="...", agent_id="...", fail_mode="fail_closed")
|
|
151
|
+
```
|
|
152
|
+
|
|
153
|
+
| Mode | When API is unreachable |
|
|
154
|
+
|------|------------------------|
|
|
155
|
+
| `fail_open` (default) | Warning logged, payment proceeds |
|
|
156
|
+
| `fail_closed` | `AbortResult` returned to x402 — payment is not made |
|
|
157
|
+
|
|
158
|
+
## Error Handling
|
|
159
|
+
|
|
160
|
+
```python
|
|
161
|
+
result = await client.evaluate_payment(
|
|
162
|
+
amount="0.10", token="USDC",
|
|
163
|
+
network="eip155:8453", pay_to="0x...",
|
|
164
|
+
)
|
|
165
|
+
if result.decision == "deny":
|
|
166
|
+
print("Blocked by:", result.triggered_rules)
|
|
167
|
+
print("Evaluation ID:", result.evaluation_id)
|
|
168
|
+
```
|
|
169
|
+
|
|
170
|
+
When using `smart402_hook()`, a denied payment returns `AbortResult` to the x402 client — the payment is not made and no exception is raised to your code. When calling `Smart402Client.evaluate()` directly, check `result.decision` — the client always returns the response, never raises on denial.
|
|
171
|
+
|
|
172
|
+
`Smart402Denied` and `Smart402Unavailable` are not raised in v0.1.
|
|
173
|
+
|
|
174
|
+
## What data leaves your machine
|
|
175
|
+
|
|
176
|
+
The SDK sends to the smart402 API:
|
|
177
|
+
- amount, token, network, recipient address
|
|
178
|
+
- agent ID and wallet address (public, not private key)
|
|
179
|
+
|
|
180
|
+
The SDK never sends:
|
|
181
|
+
- Private keys, seed phrases, or wallet passwords
|
|
182
|
+
- Signed transactions or raw transaction data
|
|
183
|
+
- Wallet balances
|
|
184
|
+
|
|
185
|
+
One HTTPS call to `POST /evaluate`. No telemetry, no analytics, no side-channel requests.
|
|
186
|
+
Verify: the SDK is ~200 lines of code. Read it.
|
|
187
|
+
|
|
188
|
+
Read the full trust model: [SECURITY.md](https://github.com/Falinkaz/smart402-python/blob/main/SECURITY.md)
|
|
189
|
+
|
|
190
|
+
## Limits
|
|
191
|
+
|
|
192
|
+
- Rate limit: 600 requests per minute per account
|
|
193
|
+
- Typical latency: 10–50ms (p50), under 200ms (p99)
|
|
194
|
+
- If the API is unreachable, `fail_open` (default) lets the payment proceed. `fail_closed` blocks it.
|
|
195
|
+
- The SDK does not retry on failure — it returns the error immediately, keeping latency predictable and letting you own retry logic.
|
|
196
|
+
- Default request timeout: 5 seconds (not configurable in v0.1)
|
|
197
|
+
|
|
198
|
+
## API Reference
|
|
199
|
+
|
|
200
|
+
Full endpoint documentation: [API.md](https://github.com/Falinkaz/smart402-python/blob/main/API.md)
|
|
201
|
+
|
|
202
|
+
## License
|
|
203
|
+
|
|
204
|
+
Apache 2.0 — see [LICENSE](https://github.com/Falinkaz/smart402-python/blob/main/LICENSE)
|
|
@@ -0,0 +1,49 @@
|
|
|
1
|
+
[build-system]
|
|
2
|
+
requires = ["setuptools>=68.0", "wheel"]
|
|
3
|
+
build-backend = "setuptools.build_meta"
|
|
4
|
+
|
|
5
|
+
[project]
|
|
6
|
+
name = "smart402"
|
|
7
|
+
version = "0.1.0"
|
|
8
|
+
description = "Deterministic risk engine for AI agent payments via x402"
|
|
9
|
+
readme = "README.md"
|
|
10
|
+
license = {text = "Apache-2.0"}
|
|
11
|
+
requires-python = ">=3.10"
|
|
12
|
+
authors = [
|
|
13
|
+
{name = "Roberto Moreno", email = "rob@smart402.com"},
|
|
14
|
+
]
|
|
15
|
+
keywords = ["x402", "ai-agents", "payments", "risk", "stablecoin", "blockchain"]
|
|
16
|
+
classifiers = [
|
|
17
|
+
"Development Status :: 4 - Beta",
|
|
18
|
+
"Intended Audience :: Developers",
|
|
19
|
+
"License :: OSI Approved :: Apache Software License",
|
|
20
|
+
"Programming Language :: Python :: 3",
|
|
21
|
+
"Programming Language :: Python :: 3.10",
|
|
22
|
+
"Programming Language :: Python :: 3.11",
|
|
23
|
+
"Programming Language :: Python :: 3.12",
|
|
24
|
+
"Programming Language :: Python :: 3.13",
|
|
25
|
+
"Topic :: Software Development :: Libraries",
|
|
26
|
+
]
|
|
27
|
+
dependencies = [
|
|
28
|
+
"httpx>=0.25.0",
|
|
29
|
+
"pydantic>=2.0",
|
|
30
|
+
]
|
|
31
|
+
|
|
32
|
+
[project.optional-dependencies]
|
|
33
|
+
x402 = ["x402[evm,httpx]>=2.3.0,<3"]
|
|
34
|
+
all = ["x402[evm,httpx]>=2.3.0,<3"]
|
|
35
|
+
dev = [
|
|
36
|
+
"pytest>=8.0",
|
|
37
|
+
"pytest-asyncio>=0.23",
|
|
38
|
+
"pytest-httpx>=0.30",
|
|
39
|
+
]
|
|
40
|
+
|
|
41
|
+
[project.urls]
|
|
42
|
+
Homepage = "https://github.com/Falinkaz/smart402-python"
|
|
43
|
+
Documentation = "https://github.com/Falinkaz/smart402-python/blob/main/API.md"
|
|
44
|
+
Repository = "https://github.com/Falinkaz/smart402-python"
|
|
45
|
+
Issues = "https://github.com/Falinkaz/smart402-python/issues"
|
|
46
|
+
|
|
47
|
+
[tool.setuptools.packages.find]
|
|
48
|
+
where = ["."]
|
|
49
|
+
include = ["smart402*"]
|
smart402-0.1.0/setup.cfg
ADDED
|
@@ -0,0 +1,12 @@
|
|
|
1
|
+
"""smart402 SDK — deterministic risk engine for AI agent payments via x402."""
|
|
2
|
+
|
|
3
|
+
from .client import Smart402Client
|
|
4
|
+
from .guard import Smart402Guard, smart402_hook
|
|
5
|
+
|
|
6
|
+
__version__ = "0.1.0"
|
|
7
|
+
|
|
8
|
+
__all__ = [
|
|
9
|
+
"smart402_hook",
|
|
10
|
+
"Smart402Guard",
|
|
11
|
+
"Smart402Client",
|
|
12
|
+
]
|
|
@@ -0,0 +1,91 @@
|
|
|
1
|
+
"""HTTP client for the smart402 API."""
|
|
2
|
+
|
|
3
|
+
import logging
|
|
4
|
+
from urllib.parse import urlparse
|
|
5
|
+
|
|
6
|
+
import httpx
|
|
7
|
+
|
|
8
|
+
from .models import EvaluateRequest, EvaluateResponse, PaymentRequirementsPayload
|
|
9
|
+
|
|
10
|
+
logger = logging.getLogger("smart402")
|
|
11
|
+
|
|
12
|
+
|
|
13
|
+
class Smart402Client:
|
|
14
|
+
"""Async HTTP client that calls the smart402 evaluation API."""
|
|
15
|
+
|
|
16
|
+
def __init__(
|
|
17
|
+
self,
|
|
18
|
+
api_key: str,
|
|
19
|
+
agent_id: str,
|
|
20
|
+
base_url: str = "https://streetsmart-api.fly.dev",
|
|
21
|
+
):
|
|
22
|
+
self.api_key = api_key
|
|
23
|
+
self.agent_id = agent_id
|
|
24
|
+
self.base_url = base_url.rstrip("/")
|
|
25
|
+
self._warn_if_insecure()
|
|
26
|
+
|
|
27
|
+
def _warn_if_insecure(self) -> None:
|
|
28
|
+
parsed = urlparse(self.base_url)
|
|
29
|
+
if parsed.scheme == "http" and parsed.hostname not in (
|
|
30
|
+
"localhost",
|
|
31
|
+
"127.0.0.1",
|
|
32
|
+
"::1",
|
|
33
|
+
):
|
|
34
|
+
logger.warning(
|
|
35
|
+
"⚠️ smart402 SDK is connecting over HTTP to %s. "
|
|
36
|
+
"API keys sent over HTTP are vulnerable to interception. "
|
|
37
|
+
"Use https:// for non-localhost connections.",
|
|
38
|
+
parsed.hostname,
|
|
39
|
+
)
|
|
40
|
+
|
|
41
|
+
async def evaluate(self, request: EvaluateRequest) -> EvaluateResponse:
|
|
42
|
+
"""Call POST /evaluate and return the decision.
|
|
43
|
+
|
|
44
|
+
Args:
|
|
45
|
+
request: EvaluateRequest with agent info and payment requirements.
|
|
46
|
+
|
|
47
|
+
Returns:
|
|
48
|
+
EvaluateResponse with decision ("approve" or "deny").
|
|
49
|
+
|
|
50
|
+
Raises:
|
|
51
|
+
httpx.HTTPStatusError: On non-2xx responses.
|
|
52
|
+
httpx.TimeoutException: If request times out.
|
|
53
|
+
"""
|
|
54
|
+
async with httpx.AsyncClient(timeout=5.0) as client:
|
|
55
|
+
response = await client.post(
|
|
56
|
+
f"{self.base_url}/evaluate",
|
|
57
|
+
json=request.model_dump(),
|
|
58
|
+
headers={"Authorization": f"Bearer {self.api_key}"},
|
|
59
|
+
)
|
|
60
|
+
response.raise_for_status()
|
|
61
|
+
return EvaluateResponse(**response.json())
|
|
62
|
+
|
|
63
|
+
async def evaluate_payment(
|
|
64
|
+
self,
|
|
65
|
+
amount: str,
|
|
66
|
+
token: str,
|
|
67
|
+
network: str,
|
|
68
|
+
pay_to: str,
|
|
69
|
+
) -> EvaluateResponse:
|
|
70
|
+
"""Convenience wrapper around evaluate() for simple one-payment calls.
|
|
71
|
+
|
|
72
|
+
Args:
|
|
73
|
+
amount: Dollar decimal string (e.g. "0.10" for $0.10 USDC).
|
|
74
|
+
token: Token symbol. smart402 v0.1 accepts "USDC" only.
|
|
75
|
+
network: CAIP-2 network identifier (e.g. "eip155:8453" for Base mainnet).
|
|
76
|
+
pay_to: Recipient wallet address.
|
|
77
|
+
|
|
78
|
+
Returns:
|
|
79
|
+
EvaluateResponse with decision ("approve" or "deny").
|
|
80
|
+
"""
|
|
81
|
+
return await self.evaluate(
|
|
82
|
+
EvaluateRequest(
|
|
83
|
+
agent_id=self.agent_id,
|
|
84
|
+
payment_requirements=PaymentRequirementsPayload(
|
|
85
|
+
amount=amount,
|
|
86
|
+
token=token,
|
|
87
|
+
network=network,
|
|
88
|
+
pay_to=pay_to,
|
|
89
|
+
),
|
|
90
|
+
)
|
|
91
|
+
)
|