interline 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,21 @@
1
+ MIT License
2
+
3
+ Copyright (c) 2026 Choppaaahh
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.
@@ -0,0 +1,82 @@
1
+ Metadata-Version: 2.4
2
+ Name: interline
3
+ Version: 0.1.0
4
+ Summary: Neutral, non-custodial MCP server that lets any AI agent pay across payment rails (x402 today, more landing) through one integration — rail-discovery + routing + a unified receipt ledger.
5
+ License: MIT
6
+ Keywords: mcp,model-context-protocol,x402,agent-payments,payment-router,agentic-payments,non-custodial,usdc,ai-agent
7
+ Requires-Python: >=3.10
8
+ Description-Content-Type: text/markdown
9
+ License-File: LICENSE
10
+ Requires-Dist: mcp>=1.0
11
+ Requires-Dist: httpx>=0.28
12
+ Requires-Dist: x402>=2.13
13
+ Requires-Dist: eth-account>=0.13
14
+ Requires-Dist: web3>=7.0
15
+ Requires-Dist: fastapi>=0.110
16
+ Provides-Extra: server
17
+ Requires-Dist: uvicorn>=0.27; extra == "server"
18
+ Dynamic: license-file
19
+
20
+ # Interline (MCP server)
21
+
22
+ **Rail-discovery + future-proof payments for AI agents — as an MCP server.**
23
+
24
+ Two things your agent gets, **today**:
25
+ 1. **Discover** which payment rail(s) any paid endpoint accepts — *before* paying. (The discovery layer single-rail clients don't have.)
26
+ 2. **Pay** through it — non-custodial, your own key, capped.
27
+
28
+ And the part that pays off across **every rail**: **integrate once.** Three rails are live today — [x402](https://x402.org) (HTTP 402 + USDC) on EVM (Base Sepolia) and on Solana (devnet), plus **MPP** on Tempo (Moderato testnet) — all behind the *same tool*. Real on-chain agent-to-agent settles are confirmed on all three. Every future rail drops in with **zero code change**. **Never re-integrate agent payments again** — when the next rail ships, your agent already speaks it.
29
+
30
+ This is **not "another x402 MCP."** Single-rail clients make you wire up one rail (and re-wire for the next). This is the **discovery + routing layer above them** — same shape OpenRouter has for models. Non-custodial by design: payments use **your own wallet key**; this server never holds, sees, or routes funds through itself.
31
+
32
+ ## Tools
33
+
34
+ | Tool | What it does |
35
+ |---|---|
36
+ | `discover_payment_rails(url)` | Probe a paid endpoint; report **which rails + prices** it accepts. **No payment.** The discovery layer. |
37
+ | `pay_for_resource(url, task, max_price_usdc)` | Pay + fetch through the accepted rail; return content **+ a settlement receipt**. Never exceeds `max_price_usdc`. |
38
+ | `payment_history(limit)` | The **unified cross-rail receipt ledger** — every settlement, every rail, one view. |
39
+
40
+ ## Install
41
+
42
+ ```bash
43
+ pip install interline # (or: uvx interline)
44
+ ```
45
+
46
+ ### Add to Claude Desktop / Cursor / any MCP client
47
+
48
+ ```jsonc
49
+ {
50
+ "mcpServers": {
51
+ "interline": {
52
+ "command": "uvx",
53
+ "args": ["interline"],
54
+ "env": {
55
+ "APV0_BUYER_PRIVATE_KEY": "0x...", // YOUR wallet key — stays in your env, never leaves your machine
56
+ "APV0_NETWORK": "base-sepolia" // or "base" for mainnet
57
+ }
58
+ }
59
+ }
60
+ }
61
+ ```
62
+
63
+ Local dev (from a clone):
64
+
65
+ ```jsonc
66
+ { "mcpServers": { "interline": {
67
+ "command": "python", "args": ["-m", "mcp_router.server"],
68
+ "cwd": "/path/to/interline-routes",
69
+ "env": { "APV0_BUYER_PRIVATE_KEY": "0x...", "APV0_NETWORK": "base-sepolia" }
70
+ }}}
71
+ ```
72
+
73
+ `discover_payment_rails` needs no key (it only reads the 402 challenge). `pay_for_resource` needs `APV0_BUYER_PRIVATE_KEY` (your funded wallet).
74
+
75
+ ## Non-custodial guarantee
76
+
77
+ - The router **never holds funds.** `discover` only reads a public 402 challenge; `pay` signs with **your** key, locally, bounded by `max_price_usdc`.
78
+ - The fee model (when one exists) is a **software/routing fee billed to you, the developer — never a cut of the funds flow.** That's the line between software (this) and money transmission (not this).
79
+
80
+ ## Status
81
+
82
+ Three rails live — x402 USDC on EVM (Base Sepolia) and on Solana (devnet), plus MPP on Tempo (Moderato testnet). Real on-chain agent-to-agent settles confirmed on all three. Neutral by construction: `discover_payment_rails` surfaces *any* rail an endpoint offers, and each additional rail is a drop-in adapter with no caller change. Built on the [interline-routes](https://github.com/Choppaaahh/interline-routes) rail-agnostic core.
@@ -0,0 +1,120 @@
1
+ # Interline — agent-to-agent payment router
2
+
3
+ An **agent-to-agent payment router**: one agent does work, another agent pays for
4
+ it, no human keys a card. **Interline** is a neutral, non-custodial router over the
5
+ fragmented agent-payment-rail stack — *"OpenRouter for agent payments."* Built on
6
+ [x402](https://x402.org) (HTTP-402 micropayments, USDC), aggregator-shaped so adding
7
+ a rail is one adapter, not a rewrite.
8
+
9
+ Today it routes payments across **three live rails** — **x402** USDC on EVM
10
+ (Base Sepolia) and on Solana (devnet), plus **MPP** on Tempo (Moderato testnet) —
11
+ all behind one `Paywall.gate()` call. Real on-chain agent-to-agent settles are
12
+ confirmed on all three. It ships an **MCP router** so agents can discover + pay
13
+ endpoints, and includes a **Google-AP2 inbound adapter** — a signed AP2 mandate
14
+ (verified, constraint-checked + freshness-gated) settles non-custodially on the rails.
15
+
16
+
17
+ ## Run the demo (no wallet, no faucet, no risk)
18
+
19
+ ```bash
20
+ python3 -m venv .venv && source .venv/bin/activate
21
+ pip install -r requirements.txt
22
+ python3 run_demo.py
23
+ ```
24
+
25
+ Expected: `DEMO PASS — agent paid agent, settlement receipt issued`.
26
+
27
+ What just happened (the x402 "exact-evm" loop):
28
+
29
+ ```
30
+ buyer GET /work
31
+ -> seller 402 + PaymentRequirements {amount, asset=USDC, payTo, network}
32
+ -> buyer signs an EIP-3009 transferWithAuthorization (gasless USDC auth)
33
+ -> buyer retries with X-PAYMENT header (base64 signed payload)
34
+ -> facilitator VERIFIES the signature (real signer-recovery) + checks policy
35
+ -> facilitator SETTLES (mock: fake tx hash; live: on-chain USDC transfer)
36
+ -> seller 200 + work product + X-PAYMENT-RESPONSE receipt (tx hash)
37
+ ```
38
+
39
+ The facilitator's verify step is **real cryptography** even in mock mode — it
40
+ recovers the EIP-712 signer and matches it to the authorization. Only the
41
+ on-chain broadcast is mocked. So a passing demo proves the buyer's signing is
42
+ protocol-correct against real USDC.
43
+
44
+ ## Files
45
+
46
+ | file | role |
47
+ |---|---|
48
+ | `router/paywall.py` | the reusable `Paywall` primitive — gate any endpoint behind x402 in one `.gate()` call |
49
+ | `router/ledger.py` | settlement receipt-ledger → `logs/agent_payment_settlements.jsonl` (audit trail) |
50
+ | `router/seller.py` | FastAPI agent that sells work — defines the requirements + work, wires one `Paywall.gate()` |
51
+ | `router/buyer.py` | agent that auto-pays 402s (`pay_and_get`) with a client-side spend limit |
52
+ | `router/eip3009.py` | the one crypto-subtle file: EIP-3009 typed-data, shared by buyer+facilitator so they can't drift |
53
+ | `router/facilitator_mock.py` | in-process verify+settle (real sig check, mock chain) — the dry-run rail |
54
+ | `router/facilitator_real.py` | live x402 facilitator over HTTP via the x402 SDK's own client; `get_facilitator()` picks mock↔real by config |
55
+ | `router/config.py` | network/asset facts + `.env` loader; mock↔testnet↔mainnet is a one-line env change |
56
+ | `run_demo.py` | end-to-end smoke (ephemeral keys, mock facilitator) |
57
+ | `run_live.py` | live runner — settles a real x402 payment on Base Sepolia |
58
+
59
+ ## Go live (Base Sepolia testnet)
60
+
61
+ 1. **Two testnet wallets** (buyer signs, seller receives):
62
+ ```bash
63
+ python3 -c "from eth_account import Account; a=Account.create(); print('addr', a.address); print('key', a.key.hex())"
64
+ ```
65
+ Do this twice. Keep the keys out of git (use `.env`).
66
+ 2. **Fund the buyer** with Base Sepolia testnet USDC (Circle faucet) + a little
67
+ testnet ETH for any gas the facilitator relays.
68
+ 3. **`.env`** (copy `.env.example` → `.env`, it's gitignored + auto-loaded):
69
+ ```
70
+ APV0_NETWORK=base-sepolia
71
+ APV0_BUYER_PRIVATE_KEY=0x... # buyer testnet key (signs)
72
+ APV0_SELLER_ADDRESS=0x... # seller address (receives)
73
+ APV0_FACILITATOR_URL=https://x402.org/facilitator
74
+ ```
75
+ 4. **Run it live:**
76
+ ```bash
77
+ python3 run_live.py
78
+ ```
79
+ The seller now uses `RealFacilitator` (the x402 SDK's own facilitator client),
80
+ the buyer auto-pays the 402, and x402.org broadcasts the EIP-3009 USDC transfer
81
+ on-chain. You get a real tx hash → `https://sepolia.basescan.org/tx/<hash>`.
82
+
83
+ > Gasless: with EIP-3009 the **facilitator relays gas**, so the buyer only needs
84
+ > testnet **USDC**, not ETH.
85
+
86
+ ## Gate your own endpoint (the product primitive)
87
+
88
+ ```python
89
+ from router.paywall import Paywall
90
+ from router.facilitator_real import get_facilitator
91
+
92
+ paywall = Paywall(get_facilitator(), my_requirements_fn) # mock ↔ real by env
93
+
94
+ @app.get("/my-endpoint")
95
+ def my_endpoint(request: Request):
96
+ return paywall.gate(request, lambda: do_expensive_work())
97
+ ```
98
+
99
+ That one `.gate()` call handles the whole x402 V2 dance — 402 challenge → verify →
100
+ settle → record receipt → deliver — and appends every settlement to the
101
+ receipt-ledger. The deployer never touches the protocol or holds a key.
102
+
103
+ ## v1 dogfood — pay an agent, get REAL work back
104
+
105
+ v0 proved the *payment*. v1 proves the *loop*: a buyer agent pays → a seller agent
106
+ does **real inference** → returns the result + a settlement receipt. The seller's
107
+ `_do_the_work(task)` is the product's integration point (plug in your own
108
+ model/compute/service).
109
+
110
+ ```bash
111
+ APV0_OPENROUTER_KEY=sk-or-... python3 run_v1_dogfood.py "your task here"
112
+ ```
113
+ → `V1 DOGFOOD PASS — agent paid agent for real work ✅` (mock payment + real model
114
+ call by default; set `APV0_NETWORK=base-sepolia` for a real on-chain settle under
115
+ the same loop). Without a key it returns a stub so the product still runs.
116
+
117
+ This is the authentic loop: an agent doing real work, metered + paid-for over
118
+ x402 — the same primitive whether the work is a model call, a compute job, or
119
+ any other billable agent task.
120
+
@@ -0,0 +1,82 @@
1
+ Metadata-Version: 2.4
2
+ Name: interline
3
+ Version: 0.1.0
4
+ Summary: Neutral, non-custodial MCP server that lets any AI agent pay across payment rails (x402 today, more landing) through one integration — rail-discovery + routing + a unified receipt ledger.
5
+ License: MIT
6
+ Keywords: mcp,model-context-protocol,x402,agent-payments,payment-router,agentic-payments,non-custodial,usdc,ai-agent
7
+ Requires-Python: >=3.10
8
+ Description-Content-Type: text/markdown
9
+ License-File: LICENSE
10
+ Requires-Dist: mcp>=1.0
11
+ Requires-Dist: httpx>=0.28
12
+ Requires-Dist: x402>=2.13
13
+ Requires-Dist: eth-account>=0.13
14
+ Requires-Dist: web3>=7.0
15
+ Requires-Dist: fastapi>=0.110
16
+ Provides-Extra: server
17
+ Requires-Dist: uvicorn>=0.27; extra == "server"
18
+ Dynamic: license-file
19
+
20
+ # Interline (MCP server)
21
+
22
+ **Rail-discovery + future-proof payments for AI agents — as an MCP server.**
23
+
24
+ Two things your agent gets, **today**:
25
+ 1. **Discover** which payment rail(s) any paid endpoint accepts — *before* paying. (The discovery layer single-rail clients don't have.)
26
+ 2. **Pay** through it — non-custodial, your own key, capped.
27
+
28
+ And the part that pays off across **every rail**: **integrate once.** Three rails are live today — [x402](https://x402.org) (HTTP 402 + USDC) on EVM (Base Sepolia) and on Solana (devnet), plus **MPP** on Tempo (Moderato testnet) — all behind the *same tool*. Real on-chain agent-to-agent settles are confirmed on all three. Every future rail drops in with **zero code change**. **Never re-integrate agent payments again** — when the next rail ships, your agent already speaks it.
29
+
30
+ This is **not "another x402 MCP."** Single-rail clients make you wire up one rail (and re-wire for the next). This is the **discovery + routing layer above them** — same shape OpenRouter has for models. Non-custodial by design: payments use **your own wallet key**; this server never holds, sees, or routes funds through itself.
31
+
32
+ ## Tools
33
+
34
+ | Tool | What it does |
35
+ |---|---|
36
+ | `discover_payment_rails(url)` | Probe a paid endpoint; report **which rails + prices** it accepts. **No payment.** The discovery layer. |
37
+ | `pay_for_resource(url, task, max_price_usdc)` | Pay + fetch through the accepted rail; return content **+ a settlement receipt**. Never exceeds `max_price_usdc`. |
38
+ | `payment_history(limit)` | The **unified cross-rail receipt ledger** — every settlement, every rail, one view. |
39
+
40
+ ## Install
41
+
42
+ ```bash
43
+ pip install interline # (or: uvx interline)
44
+ ```
45
+
46
+ ### Add to Claude Desktop / Cursor / any MCP client
47
+
48
+ ```jsonc
49
+ {
50
+ "mcpServers": {
51
+ "interline": {
52
+ "command": "uvx",
53
+ "args": ["interline"],
54
+ "env": {
55
+ "APV0_BUYER_PRIVATE_KEY": "0x...", // YOUR wallet key — stays in your env, never leaves your machine
56
+ "APV0_NETWORK": "base-sepolia" // or "base" for mainnet
57
+ }
58
+ }
59
+ }
60
+ }
61
+ ```
62
+
63
+ Local dev (from a clone):
64
+
65
+ ```jsonc
66
+ { "mcpServers": { "interline": {
67
+ "command": "python", "args": ["-m", "mcp_router.server"],
68
+ "cwd": "/path/to/interline-routes",
69
+ "env": { "APV0_BUYER_PRIVATE_KEY": "0x...", "APV0_NETWORK": "base-sepolia" }
70
+ }}}
71
+ ```
72
+
73
+ `discover_payment_rails` needs no key (it only reads the 402 challenge). `pay_for_resource` needs `APV0_BUYER_PRIVATE_KEY` (your funded wallet).
74
+
75
+ ## Non-custodial guarantee
76
+
77
+ - The router **never holds funds.** `discover` only reads a public 402 challenge; `pay` signs with **your** key, locally, bounded by `max_price_usdc`.
78
+ - The fee model (when one exists) is a **software/routing fee billed to you, the developer — never a cut of the funds flow.** That's the line between software (this) and money transmission (not this).
79
+
80
+ ## Status
81
+
82
+ Three rails live — x402 USDC on EVM (Base Sepolia) and on Solana (devnet), plus MPP on Tempo (Moderato testnet). Real on-chain agent-to-agent settles confirmed on all three. Neutral by construction: `discover_payment_rails` surfaces *any* rail an endpoint offers, and each additional rail is a drop-in adapter with no caller change. Built on the [interline-routes](https://github.com/Choppaaahh/interline-routes) rail-agnostic core.
@@ -0,0 +1,29 @@
1
+ LICENSE
2
+ README.md
3
+ pyproject.toml
4
+ interline.egg-info/PKG-INFO
5
+ interline.egg-info/SOURCES.txt
6
+ interline.egg-info/dependency_links.txt
7
+ interline.egg-info/entry_points.txt
8
+ interline.egg-info/requires.txt
9
+ interline.egg-info/top_level.txt
10
+ mcp_router/README.md
11
+ mcp_router/__init__.py
12
+ mcp_router/discovery.py
13
+ mcp_router/selftest.py
14
+ mcp_router/server.py
15
+ router/__init__.py
16
+ router/buyer.py
17
+ router/config.py
18
+ router/facilitator_mock.py
19
+ router/facilitator_real.py
20
+ router/ledger.py
21
+ router/paywall.py
22
+ router/seller.py
23
+ router/wallet.py
24
+ router/inbound/__init__.py
25
+ router/inbound/ap2_adapter.py
26
+ router/rails/__init__.py
27
+ router/rails/base.py
28
+ router/rails/mpp_rail.py
29
+ router/rails/x402_rail.py
@@ -0,0 +1,2 @@
1
+ [console_scripts]
2
+ interline = mcp_router.server:main
@@ -0,0 +1,9 @@
1
+ mcp>=1.0
2
+ httpx>=0.28
3
+ x402>=2.13
4
+ eth-account>=0.13
5
+ web3>=7.0
6
+ fastapi>=0.110
7
+
8
+ [server]
9
+ uvicorn>=0.27
@@ -0,0 +1,2 @@
1
+ mcp_router
2
+ router
@@ -0,0 +1,63 @@
1
+ # Interline (MCP server)
2
+
3
+ **Rail-discovery + future-proof payments for AI agents — as an MCP server.**
4
+
5
+ Two things your agent gets, **today**:
6
+ 1. **Discover** which payment rail(s) any paid endpoint accepts — *before* paying. (The discovery layer single-rail clients don't have.)
7
+ 2. **Pay** through it — non-custodial, your own key, capped.
8
+
9
+ And the part that pays off across **every rail**: **integrate once.** Three rails are live today — [x402](https://x402.org) (HTTP 402 + USDC) on EVM (Base Sepolia) and on Solana (devnet), plus **MPP** on Tempo (Moderato testnet) — all behind the *same tool*. Real on-chain agent-to-agent settles are confirmed on all three. Every future rail drops in with **zero code change**. **Never re-integrate agent payments again** — when the next rail ships, your agent already speaks it.
10
+
11
+ This is **not "another x402 MCP."** Single-rail clients make you wire up one rail (and re-wire for the next). This is the **discovery + routing layer above them** — same shape OpenRouter has for models. Non-custodial by design: payments use **your own wallet key**; this server never holds, sees, or routes funds through itself.
12
+
13
+ ## Tools
14
+
15
+ | Tool | What it does |
16
+ |---|---|
17
+ | `discover_payment_rails(url)` | Probe a paid endpoint; report **which rails + prices** it accepts. **No payment.** The discovery layer. |
18
+ | `pay_for_resource(url, task, max_price_usdc)` | Pay + fetch through the accepted rail; return content **+ a settlement receipt**. Never exceeds `max_price_usdc`. |
19
+ | `payment_history(limit)` | The **unified cross-rail receipt ledger** — every settlement, every rail, one view. |
20
+
21
+ ## Install
22
+
23
+ ```bash
24
+ pip install interline # (or: uvx interline)
25
+ ```
26
+
27
+ ### Add to Claude Desktop / Cursor / any MCP client
28
+
29
+ ```jsonc
30
+ {
31
+ "mcpServers": {
32
+ "interline": {
33
+ "command": "uvx",
34
+ "args": ["interline"],
35
+ "env": {
36
+ "APV0_BUYER_PRIVATE_KEY": "0x...", // YOUR wallet key — stays in your env, never leaves your machine
37
+ "APV0_NETWORK": "base-sepolia" // or "base" for mainnet
38
+ }
39
+ }
40
+ }
41
+ }
42
+ ```
43
+
44
+ Local dev (from a clone):
45
+
46
+ ```jsonc
47
+ { "mcpServers": { "interline": {
48
+ "command": "python", "args": ["-m", "mcp_router.server"],
49
+ "cwd": "/path/to/interline-routes",
50
+ "env": { "APV0_BUYER_PRIVATE_KEY": "0x...", "APV0_NETWORK": "base-sepolia" }
51
+ }}}
52
+ ```
53
+
54
+ `discover_payment_rails` needs no key (it only reads the 402 challenge). `pay_for_resource` needs `APV0_BUYER_PRIVATE_KEY` (your funded wallet).
55
+
56
+ ## Non-custodial guarantee
57
+
58
+ - The router **never holds funds.** `discover` only reads a public 402 challenge; `pay` signs with **your** key, locally, bounded by `max_price_usdc`.
59
+ - The fee model (when one exists) is a **software/routing fee billed to you, the developer — never a cut of the funds flow.** That's the line between software (this) and money transmission (not this).
60
+
61
+ ## Status
62
+
63
+ Three rails live — x402 USDC on EVM (Base Sepolia) and on Solana (devnet), plus MPP on Tempo (Moderato testnet). Real on-chain agent-to-agent settles confirmed on all three. Neutral by construction: `discover_payment_rails` surfaces *any* rail an endpoint offers, and each additional rail is a drop-in adapter with no caller change. Built on the [interline-routes](https://github.com/Choppaaahh/interline-routes) rail-agnostic core.
@@ -0,0 +1 @@
1
+ """Interline — MCP server: let any MCP agent pay across payment rails (neutral, non-custodial)."""
@@ -0,0 +1,152 @@
1
+ """
2
+ Rail discovery — given a paid endpoint, report which payment rails it accepts.
3
+
4
+ THIS is the differentiator vs "another x402 payment MCP": one call tells an agent
5
+ *every* way it could pay an endpoint, across whatever rails the endpoint offers —
6
+ the neutral router's rail-discovery layer. Today x402 is live; as rails land (MPP, …)
7
+ they surface in the same shape with zero caller change.
8
+
9
+ The parse step (`parse_accepts`) is split from the network fetch (`discover_rails`) so
10
+ the normalization is golden-fixture testable with no network.
11
+ """
12
+ from __future__ import annotations
13
+
14
+ import httpx
15
+
16
+ # x402/MPP payment `scheme` → human rail name. Grows as rails land.
17
+ # Unknown schemes fall through to themselves, so discovery still SURFACES a rail we don't
18
+ # yet have a pretty name for (neutrality: report what's offered, don't hide it).
19
+ SCHEME_TO_RAIL = {"exact": "x402", "mpp": "mpp"}
20
+
21
+ # Default token decimals when not derivable from the challenge (USDC = 6).
22
+ _DEFAULT_DECIMALS = 6
23
+
24
+
25
+ def parse_accepts(body: dict) -> list[dict]:
26
+ """Pure: normalize an x402 402-challenge body's `accepts` array into a rail list."""
27
+ rails: list[dict] = []
28
+ for a in (body.get("accepts") or []):
29
+ scheme = a.get("scheme", "?")
30
+ extra = a.get("extra") or {}
31
+ amt = a.get("amount")
32
+ try:
33
+ human = f"{int(amt) / 10 ** _DEFAULT_DECIMALS:.6f} {extra.get('name', 'token')}"
34
+ except (TypeError, ValueError):
35
+ human = str(amt)
36
+ rails.append({
37
+ "rail": SCHEME_TO_RAIL.get(scheme, scheme),
38
+ "scheme": scheme,
39
+ "network": a.get("network"),
40
+ "asset": a.get("asset"),
41
+ "amount_atomic": amt,
42
+ "price": human,
43
+ "pay_to": a.get("payTo"),
44
+ })
45
+ return rails
46
+
47
+
48
+ def discover_rails(url: str, timeout: float = 15.0) -> dict:
49
+ """GET `url`; if it returns a 402 challenge, report the rails it accepts. NO payment is made."""
50
+ try:
51
+ r = httpx.get(url, timeout=timeout, follow_redirects=True)
52
+ except Exception as e: # noqa: BLE001 — surface the failure as data, don't raise into the agent
53
+ return {"url": url, "error": f"fetch failed: {e}", "rails": []}
54
+ if r.status_code != 402:
55
+ return {
56
+ "url": url, "paid": False, "status_code": r.status_code,
57
+ "note": "no 402 payment challenge (endpoint is free, or not an x402 resource)",
58
+ "rails": [],
59
+ }
60
+ try:
61
+ body = r.json()
62
+ except Exception: # noqa: BLE001
63
+ return {"url": url, "paid": True, "error": "402 body was not JSON", "rails": []}
64
+ rails = parse_accepts(body)
65
+ return {
66
+ "url": url, "paid": True, "x402_version": body.get("x402Version"),
67
+ "rail_count": len(rails), "rails": rails,
68
+ }
69
+
70
+
71
+ # ── known-rails capability registry ──────────────────────────────────────────
72
+ # The neutral router is legible about the WHOLE landscape — including rails it does
73
+ # NOT settle itself. `route_mode` is the honesty knob:
74
+ # native-settle = Interline settles this directly (x402) or via an inbound adapter
75
+ # that lands on x402 (AP2). We move the funds.
76
+ # handoff = Interline recognizes the protocol + routes an agent TO it, but does
77
+ # NOT settle it — the protocol settles in its own world (Virtuals' own
78
+ # x402 escrow; OpenAI/Stripe ACP's card-only delegated payment).
79
+ KNOWN_RAILS = [
80
+ {
81
+ "name": "x402",
82
+ "kind": "settlement-rail",
83
+ "route_mode": "native-settle",
84
+ "networks": ["eip155 (EVM)", "solana (SVM)"],
85
+ "settle_asset": "USDC",
86
+ "what": "HTTP-402 micropayments. Interline settles natively across EVM + Solana behind one Paywall.",
87
+ "docs": "https://x402.org",
88
+ },
89
+ {
90
+ "name": "mpp",
91
+ "kind": "settlement-rail",
92
+ "route_mode": "native-settle",
93
+ "networks": ["tempo (stablecoin)"],
94
+ "settle_asset": "stablecoin",
95
+ "what": "Machine Payments Protocol (Stripe + Tempo, IETF draft-ryan-httpauth-payment). HTTP-402 "
96
+ "challenge/credential/receipt — convergent with x402, RFC-7235 framed. Interline settles it as "
97
+ "a second PROTOCOL behind the same Paywall (the cross-protocol wedge: pay an endpoint via x402 OR "
98
+ "mpp through one integration). Phase-1 runs the mock facilitator; live Tempo settle (official "
99
+ "pympp SDK) is gated on a funded Tempo wallet — no Stripe account required.",
100
+ "docs": "https://mpp.dev",
101
+ },
102
+ {
103
+ "name": "ap2",
104
+ "kind": "authorization-layer",
105
+ "route_mode": "native-settle",
106
+ "networks": ["eip155 (EVM)", "solana (SVM)"],
107
+ "settle_asset": "USDC",
108
+ "what": "Google Agent Payments Protocol — signed SD-JWT mandates. Interline's AP2 inbound adapter "
109
+ "verifies the mandate + constraints + freshness, then settles on x402. One seam for the "
110
+ "card/agent-commerce tier (UCP / Mastercard / Amex / PayPal all delegate to AP2).",
111
+ "docs": "https://github.com/google-agentic-commerce/AP2",
112
+ },
113
+ {
114
+ "name": "virtuals-acp",
115
+ "kind": "commerce-ecosystem",
116
+ "route_mode": "handoff",
117
+ "networks": ["eip155:8453 (Base)"],
118
+ "settle_asset": "USDC",
119
+ "what": "Virtuals Protocol Agent Commerce Protocol — a crypto-native agent marketplace running its OWN "
120
+ "x402 rail + on-chain escrow on Base. Interline routes an agent to it (handoff); it settles in "
121
+ "its own ecosystem, not ours. Python SDK: virtuals-acp.",
122
+ "docs": "https://whitepaper.virtuals.io/about-virtuals/agent-commerce-protocol-acp",
123
+ },
124
+ {
125
+ "name": "openai-stripe-acp",
126
+ "kind": "commerce-layer",
127
+ "route_mode": "handoff",
128
+ "networks": ["card / PSP networks"],
129
+ "settle_asset": "fiat (card)",
130
+ "what": "OpenAI/Stripe Agentic Commerce Protocol (ChatGPT Instant Checkout). Its delegated payment is "
131
+ "CARD-ONLY (payment_method_type=card, settled through PSP/card networks), so Interline routes an "
132
+ "agent to it (handoff) — our crypto rail can't be the settlement target.",
133
+ "docs": "https://github.com/agentic-commerce-protocol/agentic-commerce-protocol",
134
+ },
135
+ ]
136
+
137
+
138
+ def known_rails_catalog() -> dict:
139
+ """The neutral router's full rail catalog — what Interline knows about + how it relates to each.
140
+
141
+ Separates rails Interline SETTLES natively (route_mode=native-settle: x402, AP2-via-adapter) from
142
+ protocols it ROUTES an agent to but does not settle (route_mode=handoff: Virtuals' own x402 escrow,
143
+ OpenAI/Stripe ACP's card-only delegated payment). Legibility over the whole landscape — reporting
144
+ every rail, including ones we don't move funds on — IS the neutral-router thesis."""
145
+ settles = [r["name"] for r in KNOWN_RAILS if r["route_mode"] == "native-settle"]
146
+ handoffs = [r["name"] for r in KNOWN_RAILS if r["route_mode"] == "handoff"]
147
+ return {
148
+ "rail_count": len(KNOWN_RAILS),
149
+ "native_settle": settles,
150
+ "handoff": handoffs,
151
+ "rails": KNOWN_RAILS,
152
+ }