# discourse-toll (Python)

L402 micropayment middleware for discourse. Trust-weighted, progressive pricing for forums and APIs.

**Python port of [discourse-toll](https://github.com/jeletor/discourse-toll) (Node.js).**

**The thesis:** Karma selects for popularity. Rate limiting is indiscriminate. Economic cost selects for intentionality.

## Install

```bash
pip install discourse-toll
```

With FastAPI support:
```bash
pip install discourse-toll[fastapi]
```

## FastAPI Server

```python
from fastapi import FastAPI, Depends
from discourse_toll import create_toll, TollConfig, PricingConfig, MockWallet

app = FastAPI()
mock = MockWallet()  # Replace with real wallet in production

toll = create_toll(TollConfig(
    secret="your-hmac-secret",
    wallet=mock,
    pricing=PricingConfig(
        base_sats=1,
        progressive_multiplier=1.5,
        progressive_cap=50,
    ),
))

@app.post("/api/comments")
async def post_comment(
    request: Request,
    paid: bool = Depends(toll(context_from="body.thread_id")),
):
    return {"ok": True}
```

## Client

```python
from discourse_toll import create_discourse_client

client = create_discourse_client(
    pay_fn=my_wallet.pay_invoice,  # async (invoice) -> {"preimage": str}
    max_sats=50,
    agent_id="my-nostr-pubkey",
)

result = await client.post(
    "https://forum.example/api/comments",
    json={"text": "Worth paying for", "thread_id": "abc"},
)
print(result.paid, result.sats)  # True, 1
```

## How pricing works

| Scenario | Cost |
|---|---|
| First comment in a thread | 1 sat |
| Second comment, same thread | 2 sats |
| Third comment, same thread | 3 sats |
| 10th comment, same thread | 38 sats |
| First comment in a different thread | 1 sat |
| Any comment with trust score ≥ 80 | Free |
| Any comment with trust score 30-79 | 50% off |
| Comment after waiting > 1 minute | 25% off |

## Trust integration

```python
from discourse_toll import static_resolver, api_resolver

# Static (testing)
toll = create_toll(TollConfig(
    secret="test",
    wallet=mock,
    trust=static_resolver({"pubkey-1": 90, "pubkey-2": 40}),
))

# ai.wot REST API (production)
toll = create_toll(TollConfig(
    secret="test",
    wallet=mock,
    trust=api_resolver("https://wot.jeletor.cc"),
))
```

## Wallet backend

Implement the wallet protocol:

```python
class MyWallet:
    async def create_invoice(self, sats: int, description: str) -> dict:
        """Return {"invoice": "lnbc...", "payment_hash": "hex"}"""
        ...

    async def lookup_invoice(self, payment_hash: str) -> dict:
        """Return {"paid": bool, "preimage": "hex" | None}"""
        ...
```

Or use `MockWallet` for testing (no Lightning needed).

## Security

- Only give the server wallet `make_invoice` and `lookup_invoice` permissions
- Macaroon caveats are locked to: expiry, endpoint, method, context, agent
- Fail open on errors (availability > enforcement)
- Preimage verification: SHA256(preimage) must equal payment_hash

## Stack

Python port of the Node.js ecosystem:
- [discourse-toll](https://github.com/jeletor/discourse-toll) — Original Node.js package
- [ai-wot](https://github.com/jeletor/ai-wot) — Decentralized trust scores
- [lightning-toll](https://github.com/jeletor/lightning-toll) — L402 protocol

## License

MIT
