langchain-x402-sagg
A LangChain Tool that pays for SAGG chat-completion
requests per-request, via the x402 protocol (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
(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:
- Wrap the Go module via a subprocess or a local HTTP bridge.
- 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
pip install -e .
(Not published to PyPI - install from a local checkout.)
Usage
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:
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
- POST the prompt to
{sagg_url}/v1/chat/completions. - If the response is
402 Payment Required, parse the price (accepts[0]) from the challenge body. - Refuse if the price exceeds
max_amount_atomic. - Sign a fresh EIP-3009
TransferWithAuthorizationfor the exact amount, to the exact recipient, with a fresh random nonce (a nonce is never reused - seesigner.py's own doc comment on why that's the actual replay-protection mechanism, not anything this client itself does). - Repost the SAME prompt with an
X-PAYMENTheader carrying the signed payload. - 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
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:
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-fundedprivate_keywith the caution that implies: use a wallet holding only what you're willing to lose, and keepmax_amount_atomictight. - 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-projectrepo's Stage 3 security audit,PROJECT_STATE.mdsec 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_atomicis 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.
Download files
Download the file for your platform. If you're not sure which to choose, learn more about installing packages.
Source Distribution
Built Distribution
Filter files by name, interpreter, ABI, and platform.
If you're not sure about the file name format, learn more about wheel file names.
Copy a direct link to the current filters
File details
Details for the file langchain_x402_sagg-0.1.0.tar.gz.
File metadata
- Download URL: langchain_x402_sagg-0.1.0.tar.gz
- Upload date:
- Size: 18.4 kB
- Tags: Source
- Uploaded using Trusted Publishing? No
- Uploaded via:
twine/7.0.0 CPython/3.14.4
File hashes
| Algorithm | Hash digest | |
|---|---|---|
| SHA256 |
9f2870a45be9c821c54ded04ff86ee166e8d8b9d5ec03394e5e23f01f3589843
|
|
| MD5 |
39622f3b3b6caf8dcda92894a5bc4920
|
|
| BLAKE2b-256 |
f6d1f50c2435ec59e4fe061ab4c7d3b69530f9ab282e824dfce145f7e2539e3e
|
File details
Details for the file langchain_x402_sagg-0.1.0-py3-none-any.whl.
File metadata
- Download URL: langchain_x402_sagg-0.1.0-py3-none-any.whl
- Upload date:
- Size: 13.1 kB
- Tags: Python 3
- Uploaded using Trusted Publishing? No
- Uploaded via:
twine/7.0.0 CPython/3.14.4
File hashes
| Algorithm | Hash digest | |
|---|---|---|
| SHA256 |
c17266d5161740c90d72fea3511457c402b4b2d9d102ba4f1fb94ea8e9f92b2f
|
|
| MD5 |
ba544e0f655561b26c4234fe284c8b0e
|
|
| BLAKE2b-256 |
7ae21f189ba6105f569a582b4624205097a84760439ed5daed8561b92ab74444
|