discourse-toll (Python)
L402 micropayment middleware for discourse. Trust-weighted, progressive pricing for forums and APIs.
Python port of discourse-toll (Node.js).
The thesis: Karma selects for popularity. Rate limiting is indiscriminate. Economic cost selects for intentionality.
Install
pip install discourse-toll
With FastAPI support:
pip install discourse-toll[fastapi]
FastAPI Server
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
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
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:
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_invoiceandlookup_invoicepermissions - 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 — Original Node.js package
- ai-wot — Decentralized trust scores
- lightning-toll — L402 protocol
License
MIT
Metadata
Release files for discourse-toll 0.1.1
For a detailed explanation of source distributions (sdists) and built distributions (wheels), please see the package formats documentation.
Source distribution (sdist)
| File | Size | Uploaded | |
|---|---|---|---|
| discourse_toll-0.1.1.tar.gz | 14.7 kB | Details |
Built distribution (wheel)
| File | Interpreter | ABI | Platform | Reset |
|---|---|---|---|---|
| discourse_toll-0.1.1-py3-none-any.whl | Python 3 | none | any | Details |
Total release size: 28.6 kB
Release files / discourse_toll-0.1.1.tar.gz
| Download URL | discourse_toll-0.1.1.tar.gz |
|---|---|
| Size | 14.7 kB |
| Tags | Source |
|
SHA-256 checksum How to use checksums |
ddb83b64e135eca80649c59d7639f20431e41242d13b2d25c76530b064b2d350
|
|
BLAKE2b-256 checksum How to use checksums |
7e9d9a186ccffb6384fb1a0a3ab63dbf8a325c54e5e00f4e81d02119ee1711f7
|
| Upload date | |
|
Uploaded using Trusted Publishing? What is trusted publishing? |
No |
| Uploaded via |
twine/6.2.0 CPython/3.9.23
|
Release files / discourse_toll-0.1.1-py3-none-any.whl
| Download URL | discourse_toll-0.1.1-py3-none-any.whl |
|---|---|
| Size | 13.8 kB |
| Tags | Python 3 |
|
SHA-256 checksum How to use checksums |
01c205fd8fbfe33b42cff746e48dd4ce621ffac178c7f4765be2f8c03975ae1f
|
|
BLAKE2b-256 checksum How to use checksums |
342d654bdb935b4792752986c5374d3a3e9ec73af282154a7f9ac737d1616628
|
| Upload date | |
|
Uploaded using Trusted Publishing? What is trusted publishing? |
No |
| Uploaded via |
twine/6.2.0 CPython/3.9.23
|