Metadata-Version: 2.4
Name: powforge
Version: 0.1.0
Summary: L402 Lightning payment middleware for Python — async wrap_with_l402 wrapper for agent tools (CrewAI, AutoGen, LangChain Python). Sibling of @powforge/langchain-l402-middleware on npm.
Project-URL: Homepage, https://powforge.dev
Project-URL: Documentation, https://powforge.dev/onboard
Project-URL: Repository, https://powforge.dev
Author-email: PowForge <ops@powforge.dev>
License: MIT
Keywords: agent,autogen,bitcoin,crewai,l402,langchain,lightning,lnbits,middleware,payment,tools
Classifier: Development Status :: 4 - Beta
Classifier: Framework :: AsyncIO
Classifier: Intended Audience :: Developers
Classifier: License :: OSI Approved :: MIT License
Classifier: Programming Language :: Python :: 3
Classifier: Programming Language :: Python :: 3.9
Classifier: Programming Language :: Python :: 3.10
Classifier: Programming Language :: Python :: 3.11
Classifier: Programming Language :: Python :: 3.12
Classifier: Programming Language :: Python :: 3.13
Classifier: Topic :: Software Development :: Libraries :: Python Modules
Requires-Python: >=3.9
Requires-Dist: httpx<1.0,>=0.24
Provides-Extra: dev
Requires-Dist: pytest-asyncio>=0.23; extra == 'dev'
Requires-Dist: pytest>=8.0; extra == 'dev'
Description-Content-Type: text/markdown

# powforge

Lightning payment middleware for Python agent tools.

You wrote a CrewAI / AutoGen / LangChain tool. Someone is going to call it a
thousand times an hour, burn your OpenAI credits, and walk away. You want
the call to cost them 21 sats so they only run it when they actually need
the answer.

```python
from powforge.l402 import wrap_with_l402

async def query_market(symbol: str) -> dict:
    # your real tool here
    return {"symbol": symbol, "price": 100_000}

gated = wrap_with_l402(
    query_market,
    lnbits_url="https://lnbits.example",
    lnbits_api_key="invoice-or-admin-key",
    sats_amount=21,
)

# First call — no payment proof yet.
unpaid = await gated("BTC")
# {'error': 'payment_required', 'invoice': 'lnbc210n1...', 'payment_hash': '...', 'sats': 21, 'next_step': '...'}

# Pay the bolt11. Then call again with the payment_hash.
paid = await gated("BTC", "payment-hash-from-above")
# {'symbol': 'BTC', 'price': 100000}
```

That's it. Your tool now charges 21 sats per call.

## Why

LLM agents are economic actors. They will retry, fan out, and brute-force
any free tool you expose. A 21-sat Lightning gate makes runaway loops
self-limiting: the bill grows linearly with calls, and the agent's wallet
caps the blast radius before your wallet does.

## Install

```bash
pip install powforge
```

Python 3.9+. The only runtime dependency is `httpx` for async HTTP.

## API

### `wrap_with_l402(fn, **cfg)`

Wraps an async (or sync) callable. Returns:

```python
async def wrapped(raw_input, payment_proof: str | None = None) -> Any
```

**Two calling conventions** — agent frameworks vary in whether they let
you pass two args to a tool callback. Both work:

```python
# Two-arg form
result = await gated("BTC", "payment-hash")

# Envelope form — for single-arg frameworks (CrewAI Tool, LangChain Python)
envelope = json.dumps({
    "__payment_proof__": "payment-hash",
    "__tool_input__": "BTC",
})
result = await gated(envelope)
```

**Config:**

| arg | required | default | notes |
|---|---|---|---|
| `lnbits_url` | yes\* | — | LNBits instance base URL |
| `lnbits_api_key` | yes\* | — | Invoice key (read scope sufficient) |
| `sats_amount` | no | `10` | Price per call |
| `ttl_seconds` | no | `600` | Paid-cache TTL. `None` = forever |
| `memo` | no | `"powforge-l402"` | Invoice memo |
| `state` | no | fresh | Share a `PaymentState` across wrappers |

\* unless you inject `create_invoice_fn` + `check_paid_fn` directly
(useful for tests or non-LNBits backends).

### `create_l402_tool(name=..., description=..., func=..., **cfg)`

Returns a `{"name", "description", "func"}` dict shaped for direct
registration as a CrewAI Tool / AutoGen function tool / LangChain
DynamicTool's Python equivalents. The `func` is the L402-gated async
callable; the calling framework awaits it.

## Payment flow

1. Caller invokes the wrapped function with no `payment_proof`.
2. Wrapper mints a Lightning invoice via `POST /api/v1/payments`.
3. Wrapper returns a 402-style dict:
   ```json
   {
     "error": "payment_required",
     "invoice": "lnbc210n1...",
     "payment_hash": "...",
     "sats": 21,
     "next_step": "Pay the invoice, then re-call with the payment_hash as payment_proof."
   }
   ```
4. Caller pays the bolt11 with any Lightning wallet.
5. Caller re-invokes with the `payment_hash` as proof.
6. Wrapper verifies via `GET /api/v1/payments/<hash>`, caches the result
   for `ttl_seconds`, and runs the wrapped function.

The hash is the receipt. In the canonical L402 macaroon spec the proof is
the 32-byte preimage; this package accepts the simpler hash form because
LNBits' read endpoint takes the hash, and the wrapper still verifies it
against LNBits before unlocking the call. Callers who need cryptographic
preimage proof should use the macaroon-flavored L402 gates instead.

## Family

This package is the Python sibling of the npm packages:

- `@powforge/langchain-l402-middleware` — LangChain.js
- `@powforge/semantic-kernel-l402` — Semantic Kernel JS
- `@powforge/mcp-tool-l402` — MCP per-tool wrapper
- `@powforge/mcp-l402-gate` — full macaroon flow
- `@powforge/paymcp-l402-provider` — paymcp BasePaymentProvider

Same envelope shape across all of them.

## License

MIT.
