x402-guard 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.
- x402_guard-0.1.0/.gitignore +10 -0
- x402_guard-0.1.0/PKG-INFO +112 -0
- x402_guard-0.1.0/README.md +90 -0
- x402_guard-0.1.0/pyproject.toml +40 -0
- x402_guard-0.1.0/src/x402_guard/__init__.py +49 -0
- x402_guard-0.1.0/src/x402_guard/audit.py +27 -0
- x402_guard-0.1.0/src/x402_guard/client.py +635 -0
- x402_guard-0.1.0/src/x402_guard/config.py +171 -0
- x402_guard-0.1.0/src/x402_guard/errors.py +23 -0
- x402_guard-0.1.0/src/x402_guard/settled.py +329 -0
- x402_guard-0.1.0/src/x402_guard/store.py +156 -0
- x402_guard-0.1.0/src/x402_guard/x402client.py +344 -0
- x402_guard-0.1.0/tests/conftest.py +5 -0
- x402_guard-0.1.0/tests/test_client.py +538 -0
- x402_guard-0.1.0/tests/test_config_store.py +58 -0
|
@@ -0,0 +1,112 @@
|
|
|
1
|
+
Metadata-Version: 2.5
|
|
2
|
+
Name: x402-guard
|
|
3
|
+
Version: 0.1.0
|
|
4
|
+
Summary: Payment-path seatbelt for x402 buyer agents: Settled pre-flight, endpoint drift checks (StallRow Recheck), RouteVerdict trust gates, and spend caps enforced before any payment is signed.
|
|
5
|
+
Project-URL: Homepage, https://github.com/QuincyGB/x402-guard
|
|
6
|
+
Project-URL: Repository, https://github.com/QuincyGB/x402-guard
|
|
7
|
+
Project-URL: Issues, https://github.com/QuincyGB/x402-guard/issues
|
|
8
|
+
Author: Quincy Goertz-Brown
|
|
9
|
+
License: MIT
|
|
10
|
+
Keywords: agents,base,guard,middleware,payments,usdc,verdict-row,x402
|
|
11
|
+
Classifier: Development Status :: 3 - Alpha
|
|
12
|
+
Classifier: Intended Audience :: Developers
|
|
13
|
+
Classifier: License :: OSI Approved :: MIT License
|
|
14
|
+
Classifier: Programming Language :: Python :: 3
|
|
15
|
+
Classifier: Topic :: Software Development :: Libraries
|
|
16
|
+
Requires-Python: >=3.10
|
|
17
|
+
Requires-Dist: eth-account>=0.13
|
|
18
|
+
Requires-Dist: httpx>=0.27
|
|
19
|
+
Provides-Extra: dev
|
|
20
|
+
Requires-Dist: pytest>=8; extra == 'dev'
|
|
21
|
+
Description-Content-Type: text/markdown
|
|
22
|
+
|
|
23
|
+
# x402-guard (Python)
|
|
24
|
+
|
|
25
|
+
The payment-path seatbelt for x402 buyer agents. Install
|
|
26
|
+
`GuardedClient` once inside your paying code; from then on, every
|
|
27
|
+
x402 payment you make is pre-flighted through Verdict Row **before
|
|
28
|
+
anything is signed**:
|
|
29
|
+
|
|
30
|
+
1. **Settled pre-flight (free)** — settled.tools' scout has actually
|
|
31
|
+
bought from most endpoints worth paying. The guard asks what its
|
|
32
|
+
test purchases found. Hard veto *only* on a failed delivery or a
|
|
33
|
+
not-payable verdict; a verified delivery corroborates;
|
|
34
|
+
`nothing_to_compare` is noted, never a veto; Settled being down or
|
|
35
|
+
silent is a pass-through, never a block.
|
|
36
|
+
2. **Drift check (StallRow Recheck, $0.005, cached)** — if the
|
|
37
|
+
endpoint is new to you, or your last look is older than the TTL
|
|
38
|
+
(default 24h), the guard pays Recheck to fingerprint the
|
|
39
|
+
endpoint's live payment terms and diffs them against its local
|
|
40
|
+
store. `payTo` or asset changed → **hard block**. Price moved
|
|
41
|
+
more than the configured % (default 20%) → block. Schema /
|
|
42
|
+
description / other changes → warn. Inside the TTL the cached
|
|
43
|
+
result is used and this stage costs **zero**.
|
|
44
|
+
3. **RouteVerdict gate (optional, $0.05, cached)** — a first-ever
|
|
45
|
+
payment at or above a threshold (default $0.10) requires a
|
|
46
|
+
TRUSTED verdict ≥ your trust floor (default 80).
|
|
47
|
+
4. **Your spend caps** — per-payment, per-endpoint-daily, and
|
|
48
|
+
global-daily caps from your own config, checked against the
|
|
49
|
+
guard's local ledger *before* signing. The guard refuses first;
|
|
50
|
+
it never signs and discovers later.
|
|
51
|
+
|
|
52
|
+
Every decision — allow, warn, block, with the reason chain and what
|
|
53
|
+
the checks cost — is appended to a local audit log (JSONL) you can
|
|
54
|
+
read. Blocks raise `GuardBlocked` carrying the full decision.
|
|
55
|
+
|
|
56
|
+
```python
|
|
57
|
+
from eth_account import Account
|
|
58
|
+
from x402_guard import GuardConfig, GuardedClient
|
|
59
|
+
|
|
60
|
+
account = Account.from_key("0x...") # your key, your env — never logged
|
|
61
|
+
guard = GuardedClient(account=account, config=GuardConfig(
|
|
62
|
+
global_daily_cap_units=1_000_000, # $1.00/day across everything
|
|
63
|
+
))
|
|
64
|
+
|
|
65
|
+
result = guard.pay("https://some-service.example/v1/thing", {"input": 1})
|
|
66
|
+
print(result.outcome.response) # the service's real answer
|
|
67
|
+
print(result.decision.summary()) # what was consulted, what it cost
|
|
68
|
+
```
|
|
69
|
+
|
|
70
|
+
No signer (`account=None`) = dry-run: the pipeline runs what it can
|
|
71
|
+
for free, paid stages are noted as skipped, would-be blocks land on
|
|
72
|
+
`result.decision.blocked`, and nothing is ever paid.
|
|
73
|
+
|
|
74
|
+
## What it costs you
|
|
75
|
+
|
|
76
|
+
| Stage | Cost | When |
|
|
77
|
+
|---|---|---|
|
|
78
|
+
| Settled pre-flight | free | every payment |
|
|
79
|
+
| Recheck drift check | $0.005 | first look per endpoint per TTL (24h default) |
|
|
80
|
+
| RouteVerdict gate | $0.05 | first big payment (≥ $0.10 default) per TTL |
|
|
81
|
+
| The guard itself | $0 | it's a library |
|
|
82
|
+
|
|
83
|
+
## What it cannot do
|
|
84
|
+
|
|
85
|
+
It judges **pre-payment signals only**: the inspector's delivery
|
|
86
|
+
record, the endpoint's current terms vs. its own history, a trust
|
|
87
|
+
verdict, and your caps. It cannot judge the paid payload — whether
|
|
88
|
+
the answer you bought was *good* is your application's problem. It
|
|
89
|
+
also cannot help if an endpoint's terms change between the drift
|
|
90
|
+
check and your payment inside the same TTL window; shorten the TTL
|
|
91
|
+
if that window matters to you.
|
|
92
|
+
|
|
93
|
+
## Configuration
|
|
94
|
+
|
|
95
|
+
`GuardConfig` in code, or environment variables via
|
|
96
|
+
`GuardConfig.from_env()` (`X402_GUARD_*`: stage toggles, URLs, TTL,
|
|
97
|
+
price-change %, failure policies — `"warn"` or `"block"` when a check
|
|
98
|
+
service itself errors — USD caps, store/audit paths). Local state
|
|
99
|
+
lives in `~/.x402-guard/` (sqlite cache + spend ledger, JSONL audit
|
|
100
|
+
log) — your machine, your data, never transmitted.
|
|
101
|
+
|
|
102
|
+
An external budget object (anything with `authorize(units)` /
|
|
103
|
+
`record(units, label=)` — e.g. verdict-loop's `DailySpendCap`) can be
|
|
104
|
+
passed as `budget=`; every settled payment the guard makes, checks
|
|
105
|
+
included, is authorized and recorded against it, so an embedding app
|
|
106
|
+
keeps exactly one cap.
|
|
107
|
+
|
|
108
|
+
Part of Verdict Row: [Token Verdict](https://github.com/QuincyGB/token-verdict) ·
|
|
109
|
+
[RouteVerdict](https://github.com/QuincyGB/route-verdict) ·
|
|
110
|
+
[ListSmith](https://github.com/QuincyGB/list-smith) ·
|
|
111
|
+
[StallRow](https://github.com/QuincyGB/stall-row) ·
|
|
112
|
+
[listsmith-ci](https://github.com/QuincyGB/listsmith-ci) (the seller-side shovel)
|
|
@@ -0,0 +1,90 @@
|
|
|
1
|
+
# x402-guard (Python)
|
|
2
|
+
|
|
3
|
+
The payment-path seatbelt for x402 buyer agents. Install
|
|
4
|
+
`GuardedClient` once inside your paying code; from then on, every
|
|
5
|
+
x402 payment you make is pre-flighted through Verdict Row **before
|
|
6
|
+
anything is signed**:
|
|
7
|
+
|
|
8
|
+
1. **Settled pre-flight (free)** — settled.tools' scout has actually
|
|
9
|
+
bought from most endpoints worth paying. The guard asks what its
|
|
10
|
+
test purchases found. Hard veto *only* on a failed delivery or a
|
|
11
|
+
not-payable verdict; a verified delivery corroborates;
|
|
12
|
+
`nothing_to_compare` is noted, never a veto; Settled being down or
|
|
13
|
+
silent is a pass-through, never a block.
|
|
14
|
+
2. **Drift check (StallRow Recheck, $0.005, cached)** — if the
|
|
15
|
+
endpoint is new to you, or your last look is older than the TTL
|
|
16
|
+
(default 24h), the guard pays Recheck to fingerprint the
|
|
17
|
+
endpoint's live payment terms and diffs them against its local
|
|
18
|
+
store. `payTo` or asset changed → **hard block**. Price moved
|
|
19
|
+
more than the configured % (default 20%) → block. Schema /
|
|
20
|
+
description / other changes → warn. Inside the TTL the cached
|
|
21
|
+
result is used and this stage costs **zero**.
|
|
22
|
+
3. **RouteVerdict gate (optional, $0.05, cached)** — a first-ever
|
|
23
|
+
payment at or above a threshold (default $0.10) requires a
|
|
24
|
+
TRUSTED verdict ≥ your trust floor (default 80).
|
|
25
|
+
4. **Your spend caps** — per-payment, per-endpoint-daily, and
|
|
26
|
+
global-daily caps from your own config, checked against the
|
|
27
|
+
guard's local ledger *before* signing. The guard refuses first;
|
|
28
|
+
it never signs and discovers later.
|
|
29
|
+
|
|
30
|
+
Every decision — allow, warn, block, with the reason chain and what
|
|
31
|
+
the checks cost — is appended to a local audit log (JSONL) you can
|
|
32
|
+
read. Blocks raise `GuardBlocked` carrying the full decision.
|
|
33
|
+
|
|
34
|
+
```python
|
|
35
|
+
from eth_account import Account
|
|
36
|
+
from x402_guard import GuardConfig, GuardedClient
|
|
37
|
+
|
|
38
|
+
account = Account.from_key("0x...") # your key, your env — never logged
|
|
39
|
+
guard = GuardedClient(account=account, config=GuardConfig(
|
|
40
|
+
global_daily_cap_units=1_000_000, # $1.00/day across everything
|
|
41
|
+
))
|
|
42
|
+
|
|
43
|
+
result = guard.pay("https://some-service.example/v1/thing", {"input": 1})
|
|
44
|
+
print(result.outcome.response) # the service's real answer
|
|
45
|
+
print(result.decision.summary()) # what was consulted, what it cost
|
|
46
|
+
```
|
|
47
|
+
|
|
48
|
+
No signer (`account=None`) = dry-run: the pipeline runs what it can
|
|
49
|
+
for free, paid stages are noted as skipped, would-be blocks land on
|
|
50
|
+
`result.decision.blocked`, and nothing is ever paid.
|
|
51
|
+
|
|
52
|
+
## What it costs you
|
|
53
|
+
|
|
54
|
+
| Stage | Cost | When |
|
|
55
|
+
|---|---|---|
|
|
56
|
+
| Settled pre-flight | free | every payment |
|
|
57
|
+
| Recheck drift check | $0.005 | first look per endpoint per TTL (24h default) |
|
|
58
|
+
| RouteVerdict gate | $0.05 | first big payment (≥ $0.10 default) per TTL |
|
|
59
|
+
| The guard itself | $0 | it's a library |
|
|
60
|
+
|
|
61
|
+
## What it cannot do
|
|
62
|
+
|
|
63
|
+
It judges **pre-payment signals only**: the inspector's delivery
|
|
64
|
+
record, the endpoint's current terms vs. its own history, a trust
|
|
65
|
+
verdict, and your caps. It cannot judge the paid payload — whether
|
|
66
|
+
the answer you bought was *good* is your application's problem. It
|
|
67
|
+
also cannot help if an endpoint's terms change between the drift
|
|
68
|
+
check and your payment inside the same TTL window; shorten the TTL
|
|
69
|
+
if that window matters to you.
|
|
70
|
+
|
|
71
|
+
## Configuration
|
|
72
|
+
|
|
73
|
+
`GuardConfig` in code, or environment variables via
|
|
74
|
+
`GuardConfig.from_env()` (`X402_GUARD_*`: stage toggles, URLs, TTL,
|
|
75
|
+
price-change %, failure policies — `"warn"` or `"block"` when a check
|
|
76
|
+
service itself errors — USD caps, store/audit paths). Local state
|
|
77
|
+
lives in `~/.x402-guard/` (sqlite cache + spend ledger, JSONL audit
|
|
78
|
+
log) — your machine, your data, never transmitted.
|
|
79
|
+
|
|
80
|
+
An external budget object (anything with `authorize(units)` /
|
|
81
|
+
`record(units, label=)` — e.g. verdict-loop's `DailySpendCap`) can be
|
|
82
|
+
passed as `budget=`; every settled payment the guard makes, checks
|
|
83
|
+
included, is authorized and recorded against it, so an embedding app
|
|
84
|
+
keeps exactly one cap.
|
|
85
|
+
|
|
86
|
+
Part of Verdict Row: [Token Verdict](https://github.com/QuincyGB/token-verdict) ·
|
|
87
|
+
[RouteVerdict](https://github.com/QuincyGB/route-verdict) ·
|
|
88
|
+
[ListSmith](https://github.com/QuincyGB/list-smith) ·
|
|
89
|
+
[StallRow](https://github.com/QuincyGB/stall-row) ·
|
|
90
|
+
[listsmith-ci](https://github.com/QuincyGB/listsmith-ci) (the seller-side shovel)
|
|
@@ -0,0 +1,40 @@
|
|
|
1
|
+
[build-system]
|
|
2
|
+
requires = ["hatchling"]
|
|
3
|
+
build-backend = "hatchling.build"
|
|
4
|
+
|
|
5
|
+
[project]
|
|
6
|
+
name = "x402-guard"
|
|
7
|
+
version = "0.1.0"
|
|
8
|
+
description = "Payment-path seatbelt for x402 buyer agents: Settled pre-flight, endpoint drift checks (StallRow Recheck), RouteVerdict trust gates, and spend caps enforced before any payment is signed."
|
|
9
|
+
readme = "README.md"
|
|
10
|
+
requires-python = ">=3.10"
|
|
11
|
+
license = { text = "MIT" }
|
|
12
|
+
authors = [
|
|
13
|
+
{ name = "Quincy Goertz-Brown" },
|
|
14
|
+
]
|
|
15
|
+
keywords = ["x402", "agents", "payments", "usdc", "base", "guard", "middleware", "verdict-row"]
|
|
16
|
+
classifiers = [
|
|
17
|
+
"Development Status :: 3 - Alpha",
|
|
18
|
+
"Intended Audience :: Developers",
|
|
19
|
+
"License :: OSI Approved :: MIT License",
|
|
20
|
+
"Programming Language :: Python :: 3",
|
|
21
|
+
"Topic :: Software Development :: Libraries",
|
|
22
|
+
]
|
|
23
|
+
dependencies = [
|
|
24
|
+
"httpx>=0.27",
|
|
25
|
+
"eth-account>=0.13",
|
|
26
|
+
]
|
|
27
|
+
|
|
28
|
+
[project.optional-dependencies]
|
|
29
|
+
dev = ["pytest>=8"]
|
|
30
|
+
|
|
31
|
+
[project.urls]
|
|
32
|
+
Homepage = "https://github.com/QuincyGB/x402-guard"
|
|
33
|
+
Repository = "https://github.com/QuincyGB/x402-guard"
|
|
34
|
+
Issues = "https://github.com/QuincyGB/x402-guard/issues"
|
|
35
|
+
|
|
36
|
+
[tool.hatch.build.targets.wheel]
|
|
37
|
+
packages = ["src/x402_guard"]
|
|
38
|
+
|
|
39
|
+
[tool.pytest.ini_options]
|
|
40
|
+
testpaths = ["tests"]
|
|
@@ -0,0 +1,49 @@
|
|
|
1
|
+
"""x402-guard — the payment-path seatbelt for x402 buyer agents.
|
|
2
|
+
|
|
3
|
+
Install GuardedClient once inside your paying code; from then on
|
|
4
|
+
every x402 payment is pre-flighted before anything is signed:
|
|
5
|
+
|
|
6
|
+
1. Settled pre-flight (free) — the inspector's delivery record
|
|
7
|
+
2. Drift check (Recheck $0.005, cached daily)
|
|
8
|
+
— the endpoint's terms haven't moved
|
|
9
|
+
3. RouteVerdict gate ($0.05) — big first payments need TRUSTED
|
|
10
|
+
4. Your own spend caps — enforced before signing, never after
|
|
11
|
+
|
|
12
|
+
Blocks raise GuardBlocked with the full reason chain; every decision
|
|
13
|
+
lands in a local audit log you can read.
|
|
14
|
+
"""
|
|
15
|
+
|
|
16
|
+
from .client import GuardDecision, GuardResult, GuardedClient, StageResult
|
|
17
|
+
from .config import GuardConfig
|
|
18
|
+
from .errors import GuardBlocked
|
|
19
|
+
from .settled import SettledReport, fetch_settled_report
|
|
20
|
+
from .store import GuardStore
|
|
21
|
+
from .x402client import (
|
|
22
|
+
CallOutcome,
|
|
23
|
+
ServiceError,
|
|
24
|
+
SpendCapExceeded,
|
|
25
|
+
call_paid_endpoint,
|
|
26
|
+
format_usd,
|
|
27
|
+
load_account_from_env,
|
|
28
|
+
)
|
|
29
|
+
|
|
30
|
+
__version__ = "0.1.0"
|
|
31
|
+
|
|
32
|
+
__all__ = [
|
|
33
|
+
"GuardedClient",
|
|
34
|
+
"GuardConfig",
|
|
35
|
+
"GuardDecision",
|
|
36
|
+
"GuardResult",
|
|
37
|
+
"StageResult",
|
|
38
|
+
"GuardBlocked",
|
|
39
|
+
"GuardStore",
|
|
40
|
+
"SettledReport",
|
|
41
|
+
"fetch_settled_report",
|
|
42
|
+
"CallOutcome",
|
|
43
|
+
"ServiceError",
|
|
44
|
+
"SpendCapExceeded",
|
|
45
|
+
"call_paid_endpoint",
|
|
46
|
+
"format_usd",
|
|
47
|
+
"load_account_from_env",
|
|
48
|
+
"__version__",
|
|
49
|
+
]
|
|
@@ -0,0 +1,27 @@
|
|
|
1
|
+
"""The audit log: one JSON object per guard decision, append-only.
|
|
2
|
+
|
|
3
|
+
Every payment the guard allows, warns on, or blocks leaves a record
|
|
4
|
+
the buyer can read: what was about to be paid, which stages ran, what
|
|
5
|
+
each one found, what the checks themselves cost, and the final
|
|
6
|
+
decision with its reasons. If the guard ever stops a payment you
|
|
7
|
+
needed, this file is where it explains itself.
|
|
8
|
+
"""
|
|
9
|
+
|
|
10
|
+
from __future__ import annotations
|
|
11
|
+
|
|
12
|
+
import json
|
|
13
|
+
import threading
|
|
14
|
+
from pathlib import Path
|
|
15
|
+
|
|
16
|
+
|
|
17
|
+
class AuditLog:
|
|
18
|
+
def __init__(self, path: str | Path):
|
|
19
|
+
self.path = Path(path)
|
|
20
|
+
self.path.parent.mkdir(parents=True, exist_ok=True)
|
|
21
|
+
self._lock = threading.Lock()
|
|
22
|
+
|
|
23
|
+
def write(self, entry: dict) -> None:
|
|
24
|
+
line = json.dumps(entry, sort_keys=True)
|
|
25
|
+
with self._lock:
|
|
26
|
+
with self.path.open("a", encoding="utf-8") as fh:
|
|
27
|
+
fh.write(line + "\n")
|