Metadata-Version: 2.4
Name: langchain-x402-sagg
Version: 0.1.0
Summary: A LangChain Tool that pays per-request for SAGG LLM chat completions via the x402 protocol - no API key needed.
License: MIT
Requires-Python: >=3.9
Description-Content-Type: text/markdown
License-File: LICENSE
Requires-Dist: langchain-core>=0.3
Requires-Dist: eth-account>=0.13
Requires-Dist: requests>=2.31
Provides-Extra: dev
Requires-Dist: pytest>=7.0; extra == "dev"
Dynamic: license-file

# langchain-x402-sagg

A [LangChain](https://python.langchain.com/) `Tool` that pays for SAGG chat-completion
requests **per-request, via the [x402 protocol](https://x402.org/)** (a signed crypto
micropayment) instead of an API key. Built for autonomous agents that need to call an
LLM but have no human available to hold an API key or enter a credit card - only a
funded wallet.

**Status: extensively tested on Base Sepolia testnet; one real, successful payment
completed on Base mainnet. Not published to PyPI or GitHub - prepared for review, not
distributed.** See "Why this exists" below for the real gap this fills, and Security
below before pointing this at mainnet yourself - a single successful payment is real
evidence the mechanics work, not a claim of production-grade battle-testing.

## Why this exists

LangChain has no built-in way to pay for a tool call via x402 - see
[langchain-ai/langchain#36306](https://github.com/langchain-ai/langchain/issues/36306)
(open, unresolved). Every SAGG endpoint already speaks x402 (see the main
`sagg-project` repo's own `x402module/` and `cmd/gateway/x402billing.go`) - this
package is the missing piece that lets a LangChain agent actually use it, without
a human provisioning an API key first.

## Why pure Python, not a wrapper around the existing Go client

SAGG's own x402 client/server code (`x402module/poc/`, part of the main `sagg-project`
repo) is written in Go. Two ways to give LangChain (Python-only) access to it were
considered:

1. **Wrap the Go module** via a subprocess or a local HTTP bridge.
2. **Reimplement the signing logic in Python** (this package's choice).

EIP-712 typed-data signing and EIP-3009 (`transferWithAuthorization`) are open
cryptographic standards, not anything Go-specific - Python has a mature, widely-used
implementation already (`eth_account`, part of the same ecosystem `web3.py` is built
on). A subprocess/HTTP bridge would mean shipping (or requiring the user to build) a
platform-specific Go binary alongside a `pip install`-able package - real packaging
and distribution friction for what should be a lightweight tool, and exactly the kind
of "invented, fragile workaround" this project's own standing rule warns against when
a cleaner path exists. Reimplementing ~150 lines of well-specified, independently
testable signing logic in idiomatic Python was the more honest engineering call, not
the shortcut - and it's independently verified: `tests/test_signer.py` proves a
produced signature actually recovers to the signer's own address (not just "no
exception was raised"), and `tests/test_live_sagg_x402.py` proves the whole flow
against a real, running SAGG endpoint on real Base Sepolia testnet infrastructure.

## Installation

```bash
pip install -e .
```

(Not published to PyPI - install from a local checkout.)

## Usage

```python
from langchain_x402_sagg import make_sagg_x402_tool

tool = make_sagg_x402_tool(
    private_key="0x...",              # a Base Sepolia testnet EVM private key, funded with testnet USDC
    sagg_url="https://your-sagg-gateway.example.com",
)

result = tool.invoke({"prompt": "What is the capital of France?"})
print(result)
```

Use it in an agent like any other LangChain tool:

```python
from langchain.agents import create_agent  # or your framework's own agent constructor

agent = create_agent(model=your_llm, tools=[tool])
```

### Configuration

`make_sagg_x402_tool` accepts:

| Argument | Default | Meaning |
|---|---|---|
| `private_key` | required | Hex EVM private key. **Testnet only - see Security below.** |
| `sagg_url` | required | Base URL of the SAGG gateway (no trailing `/v1/...`). |
| `model` | `"deepseek-ai/DeepSeek-V4-Flash-0731"` | Model id to request. |
| `max_amount_atomic` | `1_000_000` (1.00 USDC) | Hard per-call payment ceiling, in USDC's smallest unit (6 decimals). A request whose price exceeds this is refused **before any signature is produced** - never silently paid over the limit. |
| `network` | `"base-sepolia"` | The network this client signs for. Must match the `network` the target `sagg_url` actually serves - see "A real safety check" below. `"base"` (mainnet) works and has been used for one real payment, but is far less tested than testnet - see Security. |

## How it works

1. POST the prompt to `{sagg_url}/v1/chat/completions`.
2. If the response is `402 Payment Required`, parse the price (`accepts[0]`) from the
   challenge body.
3. Refuse if the price exceeds `max_amount_atomic`.
4. Sign a fresh EIP-3009 `TransferWithAuthorization` for the exact amount, to the
   exact recipient, with a fresh random nonce (a nonce is never reused - see
   `signer.py`'s own doc comment on why that's the actual replay-protection
   mechanism, not anything this client itself does).
5. Repost the SAME prompt with an `X-PAYMENT` header carrying the signed payload.
6. Return the model's response text as the tool's output.

### A real safety check, added after a real failure

The first live mainnet attempt failed - not because the payment logic was broken, but
because the client signed with `network`'s configured chain id (defaulting to Base
Sepolia's `84532`) while the server it was actually pointed at was issuing a real
mainnet (`8453`) challenge. The signature was well-formed but cryptographically wrong
for the chain the server expected, and the facilitator correctly rejected it. Root
cause: nothing checked that the `network` this client was told to sign for actually
matched the network the server's own 402 challenge named.

Fixed: before signing, `post_with_payment` now compares the challenge's own `network`
field against this client's configured `network` and raises `SaggX402PaymentError`
immediately on any mismatch - before any signature is ever produced. If you see this
error, it means `network=` doesn't match the `sagg_url` you're actually calling; fix
the argument, don't work around the check.

## Testing

```bash
pip install -e ".[dev]"
pytest tests/test_signer.py tests/test_client.py -v   # fast, no network - signing + network-validation logic
```

The live end-to-end test moves real (testnet) funds and needs a reachable SAGG
endpoint, so it's opt-in:

```bash
SAGG_X402_TEST_PRIVATE_KEY=0x... \
SAGG_X402_TEST_URL=http://localhost:18080 \
RUN_LIVE_X402_TEST=1 \
pytest tests/test_live_sagg_x402.py -v -s
```

This was run for real against a locally-running SAGG gateway instance (Base Sepolia,
real facilitator, real on-chain settlement) during this package's own development -
a real partial-refund cycle (ceiling `0.000326` USDC, actual `0.000002` USDC,
refund `0.000324` USDC), with both the ceiling and refund transactions independently
confirmed on-chain (`eth_getTransactionReceipt`, `status: 0x1`) rather than trusted
from the gateway's own response alone. This same package was later also used, with
`network="base"`, for the first real payment on Base mainnet - independently confirmed
on-chain the same way.

## Security

- **Testnet is where this package has real depth of testing; mainnet has one real,
  successful payment.** Base Sepolia testnet has been exercised extensively during
  development. Base mainnet (`network="base"`) works - it's the same code path, the
  same signing logic, and one real payment has gone through it successfully with
  independent on-chain confirmation - but "it worked once" is real evidence, not a
  claim of thorough production battle-testing. Treat a mainnet-funded `private_key`
  with the caution that implies: use a wallet holding only what you're willing to
  lose, and keep `max_amount_atomic` tight.
- **The private key is held in plain Python memory** for the process's lifetime -
  the same custody model SAGG's own Go server wallet uses today (see the main
  `sagg-project` repo's Stage 3 security audit, `PROJECT_STATE.md` sec 3.147, for a
  full discussion of what that means and why it's a real, disclosed limitation, not
  a production-ready custody solution). Use a wallet that holds only what you're
  willing to lose.
- `max_amount_atomic` is a real, enforced ceiling (checked before any signature is
  produced), but it is a courtesy limit on THIS client's own behavior - it does not
  protect against a malicious or compromised server issuing a valid-looking but
  overpriced challenge below that ceiling. Set it to the smallest value your actual
  use case needs.

## What this package does NOT do

- Publish itself anywhere (no PyPI, no GitHub) - prepared for review only, per its
  own scope.
- Support streaming responses (SAGG's own streaming mode exists; this tool's first
  version only handles the non-streaming JSON response shape).
- Implement any provider failover, retries, or the fuller reliability behavior
  SAGG's own Stripe-billed path has - this is a minimal, first-version client.
