tollbooth-dpyc 0.1.10
Don't Pester Your Customer — Bitcoin Lightning micropayments for MCP servers
Requires Python: >=3.10

# Tollbooth DPYC

[![License](https://img.shields.io/badge/License-Apache_2.0-blue.svg)](https://opensource.org/licenses/Apache-2.0)
[![PyPI version](https://img.shields.io/pypi/v/tollbooth-dpyc)](https://pypi.org/project/tollbooth-dpyc/)
[![Python 3.10+](https://img.shields.io/badge/python-3.10%2B-blue)](https://www.python.org/downloads/)

<p align="center">
  <img src="https://raw.githubusercontent.com/lonniev/tollbooth-dpyc/main/docs/tollbooth-hero.png" alt="Milo drives the Lightning Turnpike — Don't Pester Your Customer" width="800">
</p>

**Don't Pester Your Customer** — Bitcoin Lightning micropayments for MCP servers.

> *The metaphors in this project are drawn with admiration from* The Phantom Tollbooth *by Norton Juster, illustrated by Jules Feiffer (1961). Milo, Tock, the Tollbooth, Dictionopolis, and Digitopolis are creations of Mr. Juster's extraordinary imagination. We just built the payment infrastructure.*

---

## The Problem

Thousands of developers are building [MCP](https://modelcontextprotocol.io/) servers — services that let AI agents like Claude interact with the world. Knowledge graphs, financial data, code repositories, medical records. Each one is a city on the map. But the turnpike between them? Wide open. No toll collectors. No sustainable economics. Just a growing network of roads that nobody's figured out how to fund.

Every MCP operator faces the same question: *how do I keep the lights on?*

Traditional API keys with monthly billing? You're running a SaaS company now. The L402 protocol — Lightning-native pay-per-request? Every single API call requires a payment negotiation. Milo's toy car stops at every intersection to fumble for exact change.

## The Solution

Tollbooth DPYC takes a different approach — one that respects everyone's time:

**Milo drives up to the tollbooth once, buys a roll of tokens with a single Lightning invoice, and drives.** No stops. No negotiations. No per-request friction. The tokens quietly decrement in the background. When the roll runs low, he buys another. The turnpike stays fast.

Prepaid credits over Bitcoin's Lightning Network, gated at the tool level, settled instantly, with no subscription management and no third-party payment processor taking a cut.

## Install

```bash
pip install tollbooth-dpyc
```

## What's in the Box

| Module | Purpose |
|--------|---------|
| `TollboothConfig` | Plain frozen dataclass — no pydantic, no env-var reading. Your host constructs it. |
| `UserLedger` | Per-user credit balance with debit/credit/rollback, daily usage logs, JSON serialization. |
| `BTCPayClient` | Async HTTP client for BTCPay Server's Greenfield API — invoices, payouts, health checks. |
| `VaultBackend` | Protocol for pluggable persistence — implement `store_ledger`, `fetch_ledger`, `snapshot_ledger`. |
| `LedgerCache` | In-memory LRU cache with write-behind flush. The hot path for all credit operations. |
| `ToolTier` | Cost tiers for tool-call metering (FREE=0, READ=1, WRITE=5, HEAVY=10 sats per call). |
| `tools.credits` | Ready-made tool implementations: `purchase_credits`, `check_payment`, `check_balance`, and more. |

## Quick Start

```python
from tollbooth import TollboothConfig, UserLedger, BTCPayClient, LedgerCache

# Configure — your host reads env vars, Tollbooth gets a plain dataclass
config = TollboothConfig(
    btcpay_host="https://your-btcpay.example.com",
    btcpay_store_id="your-store-id",
    btcpay_api_key="your-api-key",
    tollbooth_royalty_address="tollbooth@btcpay.digitalthread.link",
)

# Create a BTCPay client
async with BTCPayClient(config.btcpay_host, config.btcpay_api_key, config.btcpay_store_id) as client:
    # Create an invoice for 1000 sats
    invoice = await client.create_invoice(1000, metadata={"user": "milo"})
    print(f"Pay here: {invoice['checkoutLink']}")
```

## Configuration

`TollboothConfig` is a plain frozen dataclass. Your host application constructs it from its own settings (env vars, pydantic-settings, YAML — whatever you prefer). Tollbooth never reads environment variables directly.

| Field | Type | Default | Purpose |
|-------|------|---------|---------|
| `btcpay_host` | `str \| None` | `None` | BTCPay Server URL for creating invoices and checking payments |
| `btcpay_store_id` | `str \| None` | `None` | BTCPay store ID — each operator runs their own store |
| `btcpay_api_key` | `str \| None` | `None` | BTCPay API key — must have invoice + payout permissions |
| `btcpay_tier_config` | `str \| None` | `None` | JSON string mapping tier names to credit multipliers |
| `btcpay_user_tiers` | `str \| None` | `None` | JSON string mapping user IDs to tier names |
| `seed_balance_sats` | `int` | `0` | Free starter balance granted to new users (0 to disable) |
| `tollbooth_royalty_address` | `str \| None` | `None` | Lightning Address for the 2% royalty payout to the Tollbooth originator |
| `tollbooth_royalty_percent` | `float` | `0.02` | Royalty percentage (0.02 = 2%) |
| `tollbooth_royalty_min_sats` | `int` | `10` | Minimum royalty payout in sats (below this, no payout fires) |
| `authority_public_key` | `str \| None` | `None` | Authority's Ed25519 public key — bare base64 or full PEM. Required for `purchase_credits` (every purchase needs a valid Authority JWT). |

## Tool Functions

The `tollbooth.tools.credits` module provides ready-made implementations that your MCP server wraps as tools. Each function takes infrastructure objects (BTCPayClient, LedgerCache) as parameters — you wire them up, Tollbooth handles the logic.

| Function | Purpose |
|----------|---------|
| `purchase_credits_tool` | Creates a BTCPay invoice, records it as pending, returns a checkout link. Validates Authority certificate when `authority_public_key` is configured. |
| `verify_certificate` | Verifies an Authority-signed Ed25519 JWT. Checks signature, expiry, and anti-replay (JTI). |
| `check_payment_tool` | Polls an invoice, credits the balance on settlement, fires the royalty payout. Idempotent. |
| `check_balance_tool` | Returns current balance, usage summary, tier info, and invoice history. Read-only. |
| `restore_credits_tool` | Recovers credits from a paid invoice lost to cache/vault issues. Checks vault first, falls back to BTCPay. |
| `btcpay_status_tool` | Diagnostics: BTCPay connectivity, store name, API key permissions, royalty config. |
| `compute_low_balance_warning` | Pure function — returns a warning dict if balance is below threshold, `None` if healthy. |

## The Three-Party Settlement

Here's where the story takes a turn that even Milo wouldn't expect.

We didn't build Tollbooth to sell. We built it to **give away** — like the Massachusetts Turnpike Authority. The Authority doesn't operate every toll plaza. Independent operators run the booths. What the Authority does is simpler: it collects a small percentage of every fare that flows through infrastructure it designed.

When a user purchases credits, the settlement is three-party:

1. **Milo** pays the operator's Lightning invoice
2. **The operator's** BTCPay Server credits Milo's balance
3. **Automatically, in the background** — BTCPay creates a small payout to the Tollbooth originator's Lightning Address

A royalty. Two percent of the fare. The operator sees it transparently in their BTCPay dashboard. Milo never knows it happened.

The enforcement is both technical and social. At startup, Tollbooth inspects the operator's BTCPay API key permissions. If the key lacks payout capability, **Tollbooth refuses to start**. Not a warning. A hard stop. The social contract, made executable.

## The Economics

**For Milo (the user):** Nothing changes. Buy credits, use tools, drive the turnpike.

**For the operator:** A free, production-tested monetization framework. No license fee. The 2% royalty is a rounding error compared to the revenue you couldn't collect before. The tollbooth pays for itself on the first transaction.

**For the ecosystem:** Revenue scales with adoption, not effort. Every new MCP server that installs Tollbooth becomes a node in the Lightning economy. The infrastructure hums along — collecting its modest fare, maintaining the roads, and making sure the turnpike stays open for everyone.

*It's the transition from mining fees to transaction fees. You stop competing on compute and start collecting on flow.*

## Reference Integration

[thebrain-mcp](https://github.com/lonniev/thebrain-mcp) — the first MCP server powered by Tollbooth. A FastMCP service that gives AI agents access to TheBrain knowledge graphs, with all 40+ tools metered via Tollbooth credits.

## Architecture

Tollbooth is a three-party ecosystem:

| Repo | Role |
|------|------|
| [tollbooth-authority](https://github.com/lonniev/tollbooth-authority) | The institution — tax collection, EdDSA signing, purchase order certification |
| **tollbooth-dpyc** (this package) | The booth — operator-side credit ledger, BTCPay client, tool gating |
| [thebrain-mcp](https://github.com/lonniev/thebrain-mcp) | The first city — reference MCP server powered by Tollbooth |

See the [Three-Party Protocol diagram](https://github.com/lonniev/tollbooth-authority/blob/main/docs/diagrams/tollbooth-three-party-protocol.svg) for the full architecture.

```
tollbooth-authority               tollbooth-dpyc (this package)     your-mcp-server (consumer)
================================  ================================  ================================
EdDSA signing + tax ledger        TollboothConfig                   Settings ──constructs──> TollboothConfig
certify_purchase → JWT            UserLedger                        implements VaultBackend
Authority BTCPay                  BTCPayClient                      TOOL_COSTS maps tools to ToolTier
                                  VaultBackend (Protocol)
                                  LedgerCache + credit tools
```

Dependency flows one way: `your-mcp-server --> tollbooth-dpyc`. Authority is a network peer, not a code dependency. Only runtime dependency: `httpx`.

## DPYC Identity (Nostr npub)

Every participant in the Tollbooth ecosystem — Authorities, Operators, and Users — is identified by a [Nostr](https://nostr.com/) keypair. The `npub` (public key) is your identity on the DPYC Honor Chain. The `nsec` (private key) stays with you — never shared, never sent to a service.

Generate a keypair:

```bash
pip install nostr-sdk
python scripts/generate_nostr_keypair.py
```

Then set the appropriate environment variable in your `.env`:

| Role | Variable | Purpose |
|------|----------|---------|
| Authority | `DPYC_AUTHORITY_NPUB` | This Authority's identity on the Honor Chain |
| Authority | `DPYC_UPSTREAM_AUTHORITY_NPUB` | The Authority above in the chain (empty for Prime) |
| Operator | `DPYC_OPERATOR_NPUB` | This Operator's identity on the Honor Chain |
| Operator | `DPYC_AUTHORITY_NPUB` | The Authority this Operator is registered with |

Users provide their npub at session time via `activate_dpyc()` — no env var needed.

## Development

```bash
git clone https://github.com/lonniev/tollbooth-dpyc.git
cd tollbooth-dpyc
python -m venv venv
source venv/bin/activate
pip install -e ".[dev]"
pytest tests/ -q
```

## Further Reading

[The Phantom Tollbooth on the Lightning Turnpike](https://stablecoin.myshopify.com/blogs/our-value/the-phantom-tollbooth-on-the-lightning-turnpike) — the full story of how we're monetizing the monetization of AI APIs, and then fading to the background.

## License

Apache 2.0 — see [LICENSE](LICENSE).

---

*Because in the end, the tollbooth was never the destination. It was always just the beginning of the journey.*