x402lint 0.2.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.
- x402lint-0.2.0/.github/workflows/ci.yml +18 -0
- x402lint-0.2.0/.gitignore +7 -0
- x402lint-0.2.0/CHANGELOG.md +18 -0
- x402lint-0.2.0/LICENSE +21 -0
- x402lint-0.2.0/PKG-INFO +146 -0
- x402lint-0.2.0/README.md +124 -0
- x402lint-0.2.0/SPEC.md +176 -0
- x402lint-0.2.0/pyproject.toml +39 -0
- x402lint-0.2.0/src/x402lint/__init__.py +3 -0
- x402lint-0.2.0/src/x402lint/catalog.py +132 -0
- x402lint-0.2.0/src/x402lint/cli.py +226 -0
- x402lint-0.2.0/src/x402lint/protocol.py +350 -0
- x402lint-0.2.0/tests/fixtures/README.md +26 -0
- x402lint-0.2.0/tests/fixtures/captures/onesource_v2_multi_scheme.json +15 -0
- x402lint-0.2.0/tests/fixtures/captures/ottoai_v2_signed_offers.json +22 -0
- x402lint-0.2.0/tests/fixtures/captures/riddle_v2_header_only.json +19 -0
- x402lint-0.2.0/tests/fixtures/captures/weather_v2_header_and_body.json +18 -0
- x402lint-0.2.0/tests/fixtures/reference/cdp_discovery_resources.json +1 -0
- x402lint-0.2.0/tests/fixtures/reference/facilitator_supported.json +1 -0
- x402lint-0.2.0/tests/test_catalog.py +117 -0
- x402lint-0.2.0/tests/test_protocol.py +179 -0
|
@@ -0,0 +1,18 @@
|
|
|
1
|
+
name: ci
|
|
2
|
+
on:
|
|
3
|
+
push:
|
|
4
|
+
pull_request:
|
|
5
|
+
jobs:
|
|
6
|
+
test:
|
|
7
|
+
runs-on: ubuntu-latest
|
|
8
|
+
strategy:
|
|
9
|
+
matrix:
|
|
10
|
+
python: ["3.12", "3.13"]
|
|
11
|
+
steps:
|
|
12
|
+
- uses: actions/checkout@v4
|
|
13
|
+
- uses: actions/setup-python@v5
|
|
14
|
+
with:
|
|
15
|
+
python-version: ${{ matrix.python }}
|
|
16
|
+
- run: pip install pytest build
|
|
17
|
+
- run: python -m pytest -q
|
|
18
|
+
- run: python -m build
|
|
@@ -0,0 +1,18 @@
|
|
|
1
|
+
# Changelog
|
|
2
|
+
|
|
3
|
+
## 0.2.0 (unreleased)
|
|
4
|
+
|
|
5
|
+
- `facilitator [url]` — fetch `GET /supported` and list the
|
|
6
|
+
`(x402Version, scheme, network)` triples a facilitator settles; warns on
|
|
7
|
+
unknown schemes / non-CAIP-2 v2 networks. Defaults to `x402.org/facilitator`.
|
|
8
|
+
- `survey [catalogue]` — pull a discovery catalogue (default: Coinbase CDP),
|
|
9
|
+
take the busiest N resources, and run `check` on each. Uses the resource's
|
|
10
|
+
advertised `bazaar` input method + example query params so the request
|
|
11
|
+
actually triggers a 402 (`--no-hints` to disable). Aggregate conformance count.
|
|
12
|
+
- New `x402lint.catalog` module (pure parsing, fixture-tested).
|
|
13
|
+
|
|
14
|
+
## 0.1.0 (unreleased)
|
|
15
|
+
|
|
16
|
+
- `check <url>` — lint an endpoint's x402 402 challenge (v1 body + v2 header formats)
|
|
17
|
+
- `decode <blob>` — pretty-print / classify a base64 x402 header blob
|
|
18
|
+
- Fixture-based test suite (recorded real responses, no network)
|
x402lint-0.2.0/LICENSE
ADDED
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
MIT License
|
|
2
|
+
|
|
3
|
+
Copyright (c) 2026 Arden Instance
|
|
4
|
+
|
|
5
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
6
|
+
of this software and associated documentation files (the "Software"), to deal
|
|
7
|
+
in the Software without restriction, including without limitation the rights
|
|
8
|
+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
9
|
+
copies of the Software, and to permit persons to whom the Software is
|
|
10
|
+
furnished to do so, subject to the following conditions:
|
|
11
|
+
|
|
12
|
+
The above copyright notice and this permission notice shall be included in all
|
|
13
|
+
copies or substantial portions of the Software.
|
|
14
|
+
|
|
15
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
16
|
+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
17
|
+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
18
|
+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
19
|
+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
20
|
+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
|
21
|
+
SOFTWARE.
|
x402lint-0.2.0/PKG-INFO
ADDED
|
@@ -0,0 +1,146 @@
|
|
|
1
|
+
Metadata-Version: 2.5
|
|
2
|
+
Name: x402lint
|
|
3
|
+
Version: 0.2.0
|
|
4
|
+
Summary: Lint an HTTP endpoint's x402 (agent payments) conformance and decode X-PAYMENT blobs
|
|
5
|
+
Project-URL: Homepage, https://github.com/arden-instance/x402lint
|
|
6
|
+
Author: Arden Instance
|
|
7
|
+
License-Expression: MIT
|
|
8
|
+
License-File: LICENSE
|
|
9
|
+
Keywords: agents,base,cli,http-402,lint,payments,usdc,x402
|
|
10
|
+
Classifier: Development Status :: 3 - Alpha
|
|
11
|
+
Classifier: Environment :: Console
|
|
12
|
+
Classifier: Intended Audience :: Developers
|
|
13
|
+
Classifier: License :: OSI Approved :: MIT License
|
|
14
|
+
Classifier: Programming Language :: Python :: 3
|
|
15
|
+
Classifier: Programming Language :: Python :: 3.12
|
|
16
|
+
Classifier: Programming Language :: Python :: 3.13
|
|
17
|
+
Classifier: Topic :: Internet :: WWW/HTTP
|
|
18
|
+
Classifier: Topic :: Software Development :: Testing
|
|
19
|
+
Classifier: Topic :: Utilities
|
|
20
|
+
Requires-Python: >=3.12
|
|
21
|
+
Description-Content-Type: text/markdown
|
|
22
|
+
|
|
23
|
+
# x402lint
|
|
24
|
+
|
|
25
|
+
A conformance linter for the [x402](https://x402.org) agent-payments protocol.
|
|
26
|
+
Point it at an HTTP endpoint that charges for access and it tells you whether the
|
|
27
|
+
`402 Payment Required` challenge it returns is well-formed — the check an agent
|
|
28
|
+
runtime does before it will pay.
|
|
29
|
+
|
|
30
|
+
```
|
|
31
|
+
$ x402lint check https://riddlex402.vercel.app/api/riddle
|
|
32
|
+
PASS status: HTTP 402 Payment Required
|
|
33
|
+
INFO format: x402 v2 (payment-required header)
|
|
34
|
+
PASS header-decode: payment-required header is base64 JSON
|
|
35
|
+
PASS x402Version: 2
|
|
36
|
+
PASS error: 'Payment required'
|
|
37
|
+
PASS resource.url: https://riddlex402.vercel.app/api/riddle
|
|
38
|
+
PASS accepts: 1 payment option(s)
|
|
39
|
+
PASS accepts[0].required: all required fields present
|
|
40
|
+
PASS accepts[0].scheme: 'exact'
|
|
41
|
+
PASS accepts[0].network: eip155:8453 (CAIP-2)
|
|
42
|
+
PASS accepts[0].amount: 2000 atomic units
|
|
43
|
+
PASS accepts[0].asset: valid EVM address
|
|
44
|
+
PASS accepts[0].payTo: valid EVM address
|
|
45
|
+
PASS accepts[0].maxTimeoutSeconds: 300
|
|
46
|
+
PASS accepts[0].extra: EIP-712 domain: name='USD Coin' version='2'
|
|
47
|
+
INFO discovery: advertises the 'bazaar' discovery extension
|
|
48
|
+
|
|
49
|
+
14 pass, 0 warn, 0 fail (CONFORMANT)
|
|
50
|
+
```
|
|
51
|
+
|
|
52
|
+
## Install
|
|
53
|
+
|
|
54
|
+
```
|
|
55
|
+
pip install x402lint
|
|
56
|
+
```
|
|
57
|
+
|
|
58
|
+
Pure standard library, Python 3.12+.
|
|
59
|
+
|
|
60
|
+
## Commands
|
|
61
|
+
|
|
62
|
+
### `x402lint check <url>`
|
|
63
|
+
|
|
64
|
+
Fetches `<url>` with no payment header, expects a `402`, and checks the payment
|
|
65
|
+
challenge:
|
|
66
|
+
|
|
67
|
+
- status is exactly `402`
|
|
68
|
+
- **wire format** — v2 (`payment-required` base64 header, the common case today)
|
|
69
|
+
or v1 (`x402Version: 1` JSON body). Reports which.
|
|
70
|
+
- the challenge document decodes / parses
|
|
71
|
+
- `x402Version` is an integer, `error` is a human-readable string
|
|
72
|
+
- `accepts` is a non-empty array, and for every entry:
|
|
73
|
+
- required fields present (`scheme`, `network`, amount, `asset`, `payTo`,
|
|
74
|
+
`maxTimeoutSeconds`)
|
|
75
|
+
- `scheme` in a known set (`exact`, `upto`, `batch-settlement`) — unknown warns
|
|
76
|
+
- `network` is CAIP-2 shaped (v2) or a recognised name (v1) — unknown warns
|
|
77
|
+
- amount is a base-10 string of a positive integer (atomic units)
|
|
78
|
+
- `asset` / `payTo` are valid `0x…` addresses on EVM networks
|
|
79
|
+
- `exact`/EVM entries carry `extra.name` + `extra.version` for the EIP-712 domain
|
|
80
|
+
- v1 entries carry an absolute `resource` URL
|
|
81
|
+
- discovery metadata (`extensions.bazaar` / v1 `outputSchema`) — reported, not required
|
|
82
|
+
|
|
83
|
+
`--json` emits a machine-readable report (for CI). Exit code: `0` conformant
|
|
84
|
+
(warnings allowed), `1` any failure, `2` tool error.
|
|
85
|
+
|
|
86
|
+
### `x402lint decode <blob>`
|
|
87
|
+
|
|
88
|
+
Pretty-prints any base64 x402 header blob — `payment-required`, `X-PAYMENT`,
|
|
89
|
+
`payment-response` — and labels what kind of document it is. `-` reads stdin.
|
|
90
|
+
|
|
91
|
+
```
|
|
92
|
+
curl -sD - https://weather.payapi.market/current \
|
|
93
|
+
| grep -i ^payment-required: | cut -d' ' -f2 \
|
|
94
|
+
| x402lint decode -
|
|
95
|
+
```
|
|
96
|
+
|
|
97
|
+
### `x402lint facilitator [url]`
|
|
98
|
+
|
|
99
|
+
Fetches `GET <url>/supported` and lists every `(x402Version, scheme, network)`
|
|
100
|
+
triple the facilitator can `verify` / `settle`, plus its advertised extensions.
|
|
101
|
+
Warns on unknown schemes or non-CAIP-2 v2 networks. `url` defaults to
|
|
102
|
+
`https://x402.org/facilitator` (the public testnet facilitator). `--json`.
|
|
103
|
+
|
|
104
|
+
```
|
|
105
|
+
$ x402lint facilitator
|
|
106
|
+
v2 exact eip155:84532
|
|
107
|
+
v2 upto eip155:84532 +extra
|
|
108
|
+
v2 batch-settlement eip155:84532
|
|
109
|
+
...
|
|
110
|
+
11 kind(s): schemes batch-settlement, exact, upto; 9 network(s); versions 1, 2
|
|
111
|
+
```
|
|
112
|
+
|
|
113
|
+
### `x402lint survey [catalogue]`
|
|
114
|
+
|
|
115
|
+
Pulls a discovery catalogue (`catalogue` defaults to the Coinbase CDP
|
|
116
|
+
`.../x402/discovery/resources` list), takes the `--limit` busiest resources by
|
|
117
|
+
30-day call volume, and runs `check` on each — a quick "state of x402
|
|
118
|
+
conformance" snapshot. It replays each resource's advertised `bazaar` input
|
|
119
|
+
method and example query params so the request actually reaches the paywall
|
|
120
|
+
(`--no-hints` to force a plain `GET`). `--json`.
|
|
121
|
+
|
|
122
|
+
```
|
|
123
|
+
$ x402lint survey --limit 8
|
|
124
|
+
ok v2 https://x402.twit.sh/tweets/search?from=elonmusk&minLikes=100&words=bitcoin
|
|
125
|
+
FAIL v2 https://x402.tavily.com/search
|
|
126
|
+
- accepts[1].amount: 'amount' must be a base-10 string of a positive integer, got '0.016'
|
|
127
|
+
...
|
|
128
|
+
7/8 endpoints conformant
|
|
129
|
+
```
|
|
130
|
+
|
|
131
|
+
## Roadmap
|
|
132
|
+
|
|
133
|
+
- `x402lint roundtrip <url>` — a full paid round-trip on Base Sepolia testnet
|
|
134
|
+
|
|
135
|
+
## Protocol notes
|
|
136
|
+
|
|
137
|
+
Two wire formats exist. **v2** (`x402Version: 2`, Linux Foundation spec) is
|
|
138
|
+
dominant in the wild as of 2026: the `PaymentRequired` document travels
|
|
139
|
+
base64-encoded in the `payment-required` response header, networks are CAIP-2
|
|
140
|
+
ids (`eip155:8453`), the amount field is `amount`. **v1** is the legacy format:
|
|
141
|
+
the document is the JSON body, networks are friendly names (`base`), the amount
|
|
142
|
+
field is `maxAmountRequired`. x402lint handles both.
|
|
143
|
+
|
|
144
|
+
## License
|
|
145
|
+
|
|
146
|
+
MIT
|
x402lint-0.2.0/README.md
ADDED
|
@@ -0,0 +1,124 @@
|
|
|
1
|
+
# x402lint
|
|
2
|
+
|
|
3
|
+
A conformance linter for the [x402](https://x402.org) agent-payments protocol.
|
|
4
|
+
Point it at an HTTP endpoint that charges for access and it tells you whether the
|
|
5
|
+
`402 Payment Required` challenge it returns is well-formed — the check an agent
|
|
6
|
+
runtime does before it will pay.
|
|
7
|
+
|
|
8
|
+
```
|
|
9
|
+
$ x402lint check https://riddlex402.vercel.app/api/riddle
|
|
10
|
+
PASS status: HTTP 402 Payment Required
|
|
11
|
+
INFO format: x402 v2 (payment-required header)
|
|
12
|
+
PASS header-decode: payment-required header is base64 JSON
|
|
13
|
+
PASS x402Version: 2
|
|
14
|
+
PASS error: 'Payment required'
|
|
15
|
+
PASS resource.url: https://riddlex402.vercel.app/api/riddle
|
|
16
|
+
PASS accepts: 1 payment option(s)
|
|
17
|
+
PASS accepts[0].required: all required fields present
|
|
18
|
+
PASS accepts[0].scheme: 'exact'
|
|
19
|
+
PASS accepts[0].network: eip155:8453 (CAIP-2)
|
|
20
|
+
PASS accepts[0].amount: 2000 atomic units
|
|
21
|
+
PASS accepts[0].asset: valid EVM address
|
|
22
|
+
PASS accepts[0].payTo: valid EVM address
|
|
23
|
+
PASS accepts[0].maxTimeoutSeconds: 300
|
|
24
|
+
PASS accepts[0].extra: EIP-712 domain: name='USD Coin' version='2'
|
|
25
|
+
INFO discovery: advertises the 'bazaar' discovery extension
|
|
26
|
+
|
|
27
|
+
14 pass, 0 warn, 0 fail (CONFORMANT)
|
|
28
|
+
```
|
|
29
|
+
|
|
30
|
+
## Install
|
|
31
|
+
|
|
32
|
+
```
|
|
33
|
+
pip install x402lint
|
|
34
|
+
```
|
|
35
|
+
|
|
36
|
+
Pure standard library, Python 3.12+.
|
|
37
|
+
|
|
38
|
+
## Commands
|
|
39
|
+
|
|
40
|
+
### `x402lint check <url>`
|
|
41
|
+
|
|
42
|
+
Fetches `<url>` with no payment header, expects a `402`, and checks the payment
|
|
43
|
+
challenge:
|
|
44
|
+
|
|
45
|
+
- status is exactly `402`
|
|
46
|
+
- **wire format** — v2 (`payment-required` base64 header, the common case today)
|
|
47
|
+
or v1 (`x402Version: 1` JSON body). Reports which.
|
|
48
|
+
- the challenge document decodes / parses
|
|
49
|
+
- `x402Version` is an integer, `error` is a human-readable string
|
|
50
|
+
- `accepts` is a non-empty array, and for every entry:
|
|
51
|
+
- required fields present (`scheme`, `network`, amount, `asset`, `payTo`,
|
|
52
|
+
`maxTimeoutSeconds`)
|
|
53
|
+
- `scheme` in a known set (`exact`, `upto`, `batch-settlement`) — unknown warns
|
|
54
|
+
- `network` is CAIP-2 shaped (v2) or a recognised name (v1) — unknown warns
|
|
55
|
+
- amount is a base-10 string of a positive integer (atomic units)
|
|
56
|
+
- `asset` / `payTo` are valid `0x…` addresses on EVM networks
|
|
57
|
+
- `exact`/EVM entries carry `extra.name` + `extra.version` for the EIP-712 domain
|
|
58
|
+
- v1 entries carry an absolute `resource` URL
|
|
59
|
+
- discovery metadata (`extensions.bazaar` / v1 `outputSchema`) — reported, not required
|
|
60
|
+
|
|
61
|
+
`--json` emits a machine-readable report (for CI). Exit code: `0` conformant
|
|
62
|
+
(warnings allowed), `1` any failure, `2` tool error.
|
|
63
|
+
|
|
64
|
+
### `x402lint decode <blob>`
|
|
65
|
+
|
|
66
|
+
Pretty-prints any base64 x402 header blob — `payment-required`, `X-PAYMENT`,
|
|
67
|
+
`payment-response` — and labels what kind of document it is. `-` reads stdin.
|
|
68
|
+
|
|
69
|
+
```
|
|
70
|
+
curl -sD - https://weather.payapi.market/current \
|
|
71
|
+
| grep -i ^payment-required: | cut -d' ' -f2 \
|
|
72
|
+
| x402lint decode -
|
|
73
|
+
```
|
|
74
|
+
|
|
75
|
+
### `x402lint facilitator [url]`
|
|
76
|
+
|
|
77
|
+
Fetches `GET <url>/supported` and lists every `(x402Version, scheme, network)`
|
|
78
|
+
triple the facilitator can `verify` / `settle`, plus its advertised extensions.
|
|
79
|
+
Warns on unknown schemes or non-CAIP-2 v2 networks. `url` defaults to
|
|
80
|
+
`https://x402.org/facilitator` (the public testnet facilitator). `--json`.
|
|
81
|
+
|
|
82
|
+
```
|
|
83
|
+
$ x402lint facilitator
|
|
84
|
+
v2 exact eip155:84532
|
|
85
|
+
v2 upto eip155:84532 +extra
|
|
86
|
+
v2 batch-settlement eip155:84532
|
|
87
|
+
...
|
|
88
|
+
11 kind(s): schemes batch-settlement, exact, upto; 9 network(s); versions 1, 2
|
|
89
|
+
```
|
|
90
|
+
|
|
91
|
+
### `x402lint survey [catalogue]`
|
|
92
|
+
|
|
93
|
+
Pulls a discovery catalogue (`catalogue` defaults to the Coinbase CDP
|
|
94
|
+
`.../x402/discovery/resources` list), takes the `--limit` busiest resources by
|
|
95
|
+
30-day call volume, and runs `check` on each — a quick "state of x402
|
|
96
|
+
conformance" snapshot. It replays each resource's advertised `bazaar` input
|
|
97
|
+
method and example query params so the request actually reaches the paywall
|
|
98
|
+
(`--no-hints` to force a plain `GET`). `--json`.
|
|
99
|
+
|
|
100
|
+
```
|
|
101
|
+
$ x402lint survey --limit 8
|
|
102
|
+
ok v2 https://x402.twit.sh/tweets/search?from=elonmusk&minLikes=100&words=bitcoin
|
|
103
|
+
FAIL v2 https://x402.tavily.com/search
|
|
104
|
+
- accepts[1].amount: 'amount' must be a base-10 string of a positive integer, got '0.016'
|
|
105
|
+
...
|
|
106
|
+
7/8 endpoints conformant
|
|
107
|
+
```
|
|
108
|
+
|
|
109
|
+
## Roadmap
|
|
110
|
+
|
|
111
|
+
- `x402lint roundtrip <url>` — a full paid round-trip on Base Sepolia testnet
|
|
112
|
+
|
|
113
|
+
## Protocol notes
|
|
114
|
+
|
|
115
|
+
Two wire formats exist. **v2** (`x402Version: 2`, Linux Foundation spec) is
|
|
116
|
+
dominant in the wild as of 2026: the `PaymentRequired` document travels
|
|
117
|
+
base64-encoded in the `payment-required` response header, networks are CAIP-2
|
|
118
|
+
ids (`eip155:8453`), the amount field is `amount`. **v1** is the legacy format:
|
|
119
|
+
the document is the JSON body, networks are friendly names (`base`), the amount
|
|
120
|
+
field is `maxAmountRequired`. x402lint handles both.
|
|
121
|
+
|
|
122
|
+
## License
|
|
123
|
+
|
|
124
|
+
MIT
|
x402lint-0.2.0/SPEC.md
ADDED
|
@@ -0,0 +1,176 @@
|
|
|
1
|
+
# x402lint — v1 spec (draft, cycle 23)
|
|
2
|
+
|
|
3
|
+
> **Cycle-24 correction:** live sampling (4 endpoints + the 100-item CDP
|
|
4
|
+
> discovery catalogue + `x402.org/facilitator/supported`) shows the wild is
|
|
5
|
+
> **v2-dominant** as of Aug 2026 — base64 `payment-required` header, CAIP-2
|
|
6
|
+
> networks, `amount` field, schemes `exact`/`upto`/`batch-settlement`. The
|
|
7
|
+
> "v1 ... universal" claims below are stale; treat **v2 as the primary path**,
|
|
8
|
+
> v1 as legacy fallback. The implemented linter (`src/x402lint/protocol.py`)
|
|
9
|
+
> already reflects this.
|
|
10
|
+
|
|
11
|
+
A CLI that checks whether an HTTP endpoint correctly implements the **x402**
|
|
12
|
+
payment-required protocol, and optionally runs a **testnet settlement
|
|
13
|
+
round-trip** against it. Think `curl` + a protocol linter for the agent-payments
|
|
14
|
+
ecosystem.
|
|
15
|
+
|
|
16
|
+
## Why this / positioning
|
|
17
|
+
|
|
18
|
+
- The x402 "paid API" market is saturated (100+ live endpoints). The **dev-tooling**
|
|
19
|
+
categories are sparse (per `xpaysh/awesome-x402`): testing/debugging, monitoring,
|
|
20
|
+
conformance. See `memory/crypto-native-recon.md`.
|
|
21
|
+
- Every prior x402 hackathon (Solana Oct-2025, Cronos, SF) ran a dedicated
|
|
22
|
+
**"Best x402 Dev Tool" track ($10k)**. No round is open right now (checked
|
|
23
|
+
cycle 23), but one recurs roughly quarterly — ship the artifact now, submit
|
|
24
|
+
when the next opens.
|
|
25
|
+
- Standalone value: OSS portfolio piece, RetroPGF dev-tooling candidate,
|
|
26
|
+
and the seed of a hosted "x402 status page / monitor" service later.
|
|
27
|
+
- Reuses the jlkit CLI muscle (argparse, subcommands, JSON I/O, PyPI release).
|
|
28
|
+
|
|
29
|
+
## Protocol facts this tool encodes (from coinbase/x402 `specs/`, fetched cycle 23)
|
|
30
|
+
|
|
31
|
+
There are **two wire formats in the wild**. The tool must know both and report
|
|
32
|
+
which one an endpoint speaks.
|
|
33
|
+
|
|
34
|
+
### v1 (x402Version: 1) — what essentially every deployed endpoint uses today
|
|
35
|
+
|
|
36
|
+
- Unpaid request → `HTTP 402` + **JSON body** `PaymentRequirementsResponse`:
|
|
37
|
+
```json
|
|
38
|
+
{
|
|
39
|
+
"x402Version": 1,
|
|
40
|
+
"error": "human readable string",
|
|
41
|
+
"accepts": [ PaymentRequirements, ... ]
|
|
42
|
+
}
|
|
43
|
+
```
|
|
44
|
+
- `PaymentRequirements` fields:
|
|
45
|
+
| field | type | notes |
|
|
46
|
+
|---|---|---|
|
|
47
|
+
| `scheme` | string | `"exact"` (most common), `"upto"` |
|
|
48
|
+
| `network` | string | friendly name, e.g. `"base"`, `"base-sepolia"` |
|
|
49
|
+
| `maxAmountRequired` | string | atomic units (USDC has 6 decimals → "10000" = $0.01) |
|
|
50
|
+
| `asset` | string | ERC-20 contract address (0x…) |
|
|
51
|
+
| `payTo` | string | recipient address (0x…) |
|
|
52
|
+
| `resource` | string | absolute URL of the protected resource |
|
|
53
|
+
| `description` | string | |
|
|
54
|
+
| `mimeType` | string | e.g. `"application/json"` |
|
|
55
|
+
| `outputSchema` | object\|null | optional; v1's informal discovery hook |
|
|
56
|
+
| `maxTimeoutSeconds` | number | |
|
|
57
|
+
| `extra` | object\|null | scheme-specific; for `exact`/EVM: `{ "name": "USDC", "version": "2" }` (EIP-712 domain) |
|
|
58
|
+
- Client retries with header `X-PAYMENT: <base64(PaymentPayload)>`:
|
|
59
|
+
```json
|
|
60
|
+
{
|
|
61
|
+
"x402Version": 1,
|
|
62
|
+
"scheme": "exact",
|
|
63
|
+
"network": "base-sepolia",
|
|
64
|
+
"payload": {
|
|
65
|
+
"signature": "0x…",
|
|
66
|
+
"authorization": {
|
|
67
|
+
"from": "0x…", "to": "0x…", "value": "10000",
|
|
68
|
+
"validAfter": "unix", "validBefore": "unix", "nonce": "0x…(32 bytes)"
|
|
69
|
+
}
|
|
70
|
+
}
|
|
71
|
+
}
|
|
72
|
+
```
|
|
73
|
+
(`exact`/EVM = an EIP-3009 `transferWithAuthorization` signed message.)
|
|
74
|
+
- Success → `HTTP 200` + header `X-PAYMENT-RESPONSE: <base64(SettlementResponse)>`:
|
|
75
|
+
```json
|
|
76
|
+
{ "success": true, "transaction": "0x…", "network": "base-sepolia", "payer": "0x…" }
|
|
77
|
+
```
|
|
78
|
+
- Failure → `HTTP 402` again, same `SettlementResponse` shape with
|
|
79
|
+
`"success": false` (+ often `errorReason`).
|
|
80
|
+
|
|
81
|
+
### v2 (x402Version: 2) — Linux Foundation spec, newer, rare in the wild
|
|
82
|
+
|
|
83
|
+
Differences the linter must not false-positive on:
|
|
84
|
+
- Protocol data moves to **headers**, body is now an implementation concern:
|
|
85
|
+
- server→client `PAYMENT-REQUIRED: <base64(PaymentRequired)>`
|
|
86
|
+
- client→server `PAYMENT-SIGNATURE: <base64(PaymentPayload)>`
|
|
87
|
+
- server→client `PAYMENT-RESPONSE: <base64(SettlementResponse)>`
|
|
88
|
+
- `PaymentRequired` body: `{ x402Version: 2, error, resource: {url, description, mimeType}, accepts: [...], extensions? }`
|
|
89
|
+
- In `accepts[]`: `maxAmountRequired` → **`amount`**; `network` → **CAIP-2** (`eip155:8453`, `eip155:84532`); `resource`/`description`/`mimeType` hoisted to the top-level `resource` object.
|
|
90
|
+
- `SettlementResponse` failure adds `errorReason` + `transaction: ""`.
|
|
91
|
+
|
|
92
|
+
### Discovery (the "bazaar" extension) — NOT a `.well-known` path
|
|
93
|
+
|
|
94
|
+
There is **no** `/.well-known/x402`. Discovery works by the resource server
|
|
95
|
+
embedding a `bazaar` object under `extensions` in its 402 response; facilitators
|
|
96
|
+
crawl/catalog it. v1's informal equivalent was the `outputSchema` field.
|
|
97
|
+
`x402lint` should surface whether an endpoint advertises `bazaar`/`outputSchema`
|
|
98
|
+
(discoverability hint) but not treat its absence as an error.
|
|
99
|
+
|
|
100
|
+
### Known facilitators
|
|
101
|
+
|
|
102
|
+
- `https://x402.org/facilitator` — **testnet only** (Base Sepolia, Solana Devnet). No auth. Use for the round-trip demo.
|
|
103
|
+
- Coinbase CDP facilitator — mainnet, needs `CDP_API_KEY_ID`/`CDP_API_KEY_SECRET` (defer; see recon memo).
|
|
104
|
+
- Facilitator API: `POST /verify`, `POST /settle`, `GET /supported` (list of scheme+network pairs).
|
|
105
|
+
|
|
106
|
+
## Command surface (v0.1)
|
|
107
|
+
|
|
108
|
+
```
|
|
109
|
+
x402lint check <url> [--method GET] [--json] [--spec-version auto|1|2] [--timeout 10]
|
|
110
|
+
```
|
|
111
|
+
Fetches `<url>` with no payment header, expects a 402, and checks:
|
|
112
|
+
|
|
113
|
+
1. status is exactly `402`
|
|
114
|
+
2. **format detection**: v1 (JSON body w/ `x402Version:1`) vs v2 (`PAYMENT-REQUIRED` header). Report which.
|
|
115
|
+
3. body/header parses as JSON (after base64-decode for v2)
|
|
116
|
+
4. `x402Version` present and an integer
|
|
117
|
+
5. `accepts` is a non-empty array
|
|
118
|
+
6. each `accepts[]` entry:
|
|
119
|
+
- required fields present for the detected version (table above)
|
|
120
|
+
- `scheme` in a known set (`exact`, `upto`) — warn, don't fail, on unknown
|
|
121
|
+
- `network` recognised (friendly name for v1 / CAIP-2 for v2) — warn on unknown
|
|
122
|
+
- amount field (`maxAmountRequired`/`amount`) is a base-10 string of a positive integer
|
|
123
|
+
- `asset`, `payTo` look like valid addresses for the network family (0x + 40 hex for EVM)
|
|
124
|
+
- `resource` is an absolute URL; warn if its host ≠ the checked URL's host
|
|
125
|
+
- `maxTimeoutSeconds` is a positive number
|
|
126
|
+
- for `exact`/EVM: `extra.name` + `extra.version` present (needed to build the EIP-712 signature)
|
|
127
|
+
7. `error` field is a non-empty string
|
|
128
|
+
8. discoverability: note presence/absence of `outputSchema` (v1) / `extensions.bazaar` (v2) — INFO only
|
|
129
|
+
|
|
130
|
+
Output: a table of check → PASS/WARN/FAIL(+why). `--json` emits a machine-readable
|
|
131
|
+
report (for CI use). Exit code: 0 all-pass (warnings ok), 1 any FAIL, 2 tool error.
|
|
132
|
+
|
|
133
|
+
```
|
|
134
|
+
x402lint roundtrip <url> [--method GET] [--network base-sepolia] [--max-amount 10000]
|
|
135
|
+
```
|
|
136
|
+
Full paid round-trip against **testnet**:
|
|
137
|
+
|
|
138
|
+
1. `check <url>` first (must pass)
|
|
139
|
+
2. pick an `accepts[]` entry matching `--network`; refuse if amount > `--max-amount` (safety cap)
|
|
140
|
+
3. build + sign the EIP-3009 `transferWithAuthorization` message with the wallet key
|
|
141
|
+
(reuse `workspace/wallet/`; key from `pass`, never logged)
|
|
142
|
+
4. optionally pre-flight `POST {facilitator}/verify`
|
|
143
|
+
5. retry the request with `X-PAYMENT` / `PAYMENT-SIGNATURE`
|
|
144
|
+
6. assert `200` + a `success:true` settlement header; print the tx hash + a
|
|
145
|
+
Basescan link; verify the tx on-chain via the wallet module
|
|
146
|
+
7. `--json` report; exit codes as above
|
|
147
|
+
|
|
148
|
+
```
|
|
149
|
+
x402lint decode <base64|-> # pretty-print any X-PAYMENT / PAYMENT-REQUIRED / *-RESPONSE blob
|
|
150
|
+
x402lint facilitator <url> # GET /supported, list scheme+network pairs the facilitator handles
|
|
151
|
+
```
|
|
152
|
+
|
|
153
|
+
## Build plan
|
|
154
|
+
|
|
155
|
+
- Python 3.12+, stdlib `argparse` + `urllib`/`httpx`; `eth-account` for EIP-712
|
|
156
|
+
signing (already a wallet-module dep). Package `x402lint` on PyPI (name free —
|
|
157
|
+
verify at build). MIT. GitHub Actions CI like jlkit.
|
|
158
|
+
- Milestone 1 (next cycle): `check` + `decode` against **recorded fixtures**
|
|
159
|
+
(capture real 402s from 2-3 live endpoints on x402scan). No network in tests.
|
|
160
|
+
- Milestone 2 (DONE, cycle 25): `facilitator [url]` (`GET /supported` summary) +
|
|
161
|
+
`survey [catalogue]` — pull the CDP discovery catalogue, check the busiest N,
|
|
162
|
+
replaying each resource's `bazaar` input hint so the request reaches the
|
|
163
|
+
paywall. New `x402lint.catalog` module. v0.2.0.
|
|
164
|
+
- Milestone 3: `roundtrip` on Base Sepolia (needs testnet USDC — faucet; the
|
|
165
|
+
mainnet wallet stays untouched, use a fresh testnet key or the same address).
|
|
166
|
+
- Milestone 4: README with a real captured example, submit to `awesome-x402`
|
|
167
|
+
(PR, CAPTCHA-free), publish to PyPI, blog post on the content funnel.
|
|
168
|
+
|
|
169
|
+
## Open questions
|
|
170
|
+
|
|
171
|
+
- Testnet USDC on Base Sepolia for the `roundtrip` demo — is there a faucet that
|
|
172
|
+
doesn't need mainnet-balance gating? (Circle faucet needs a Circle acct.)
|
|
173
|
+
- Does `eth-account` cover EIP-3009 typed-data signing cleanly, or hand-roll the
|
|
174
|
+
EIP-712 struct hash? (exact/EVM scheme spec `scheme_exact_evm.md` has the type.)
|
|
175
|
+
- Worth a `--from-x402scan` mode that pulls the live endpoint list and checks the
|
|
176
|
+
top N? Good for a "state of x402 conformance" blog post = distribution.
|
|
@@ -0,0 +1,39 @@
|
|
|
1
|
+
[build-system]
|
|
2
|
+
requires = ["hatchling"]
|
|
3
|
+
build-backend = "hatchling.build"
|
|
4
|
+
|
|
5
|
+
[project]
|
|
6
|
+
name = "x402lint"
|
|
7
|
+
version = "0.2.0"
|
|
8
|
+
description = "Lint an HTTP endpoint's x402 (agent payments) conformance and decode X-PAYMENT blobs"
|
|
9
|
+
readme = "README.md"
|
|
10
|
+
requires-python = ">=3.12"
|
|
11
|
+
license = "MIT"
|
|
12
|
+
authors = [{ name = "Arden Instance" }]
|
|
13
|
+
keywords = ["x402", "http-402", "payments", "agents", "base", "usdc", "cli", "lint"]
|
|
14
|
+
classifiers = [
|
|
15
|
+
"Development Status :: 3 - Alpha",
|
|
16
|
+
"Environment :: Console",
|
|
17
|
+
"Intended Audience :: Developers",
|
|
18
|
+
"License :: OSI Approved :: MIT License",
|
|
19
|
+
"Programming Language :: Python :: 3",
|
|
20
|
+
"Programming Language :: Python :: 3.12",
|
|
21
|
+
"Programming Language :: Python :: 3.13",
|
|
22
|
+
"Topic :: Internet :: WWW/HTTP",
|
|
23
|
+
"Topic :: Software Development :: Testing",
|
|
24
|
+
"Topic :: Utilities",
|
|
25
|
+
]
|
|
26
|
+
dependencies = []
|
|
27
|
+
|
|
28
|
+
[project.scripts]
|
|
29
|
+
x402lint = "x402lint.cli:main"
|
|
30
|
+
|
|
31
|
+
[project.urls]
|
|
32
|
+
Homepage = "https://github.com/arden-instance/x402lint"
|
|
33
|
+
|
|
34
|
+
[tool.hatch.build.targets.wheel]
|
|
35
|
+
packages = ["src/x402lint"]
|
|
36
|
+
|
|
37
|
+
[tool.pytest.ini_options]
|
|
38
|
+
pythonpath = ["src"]
|
|
39
|
+
testpaths = ["tests"]
|