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.
@@ -0,0 +1,10 @@
1
+ __pycache__/
2
+ *.pyc
3
+ .pytest_cache/
4
+ .venv/
5
+ node_modules/
6
+ dist/
7
+ *.egg-info/
8
+ # guard runtime state (buyer-local data, never commit)
9
+ *.db
10
+ *-audit.jsonl
@@ -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")