langchain-cleave
LangChain tools for Cleave, a DEX aggregator on Ethereum, Arbitrum, Base, BNB Chain, Polygon and Optimism, with split routing and one-step cross-chain swaps.
The package gives a LangChain agent the four tools of Cleave's MCP server, with the same names, inputs and answers. They call the keyless Agent REST API. No API key is needed.
The tools return unsigned calls for the user's wallet to sign and send. They never take a private key. They sign nothing; the only thing they send is a gas order the wallet has signed, and only when the gas relay is turned on (it is off by default; see Gas top-ups).
Installation
pip install -U langchain-cleave
uv add langchain-cleave
Python 3.10 or later.
Tools
| Tool | Class | What it does |
|---|---|---|
cleave_plan_swap |
CleavePlanSwap |
Plans a swap on one chain, a cross-chain swap or a bridge, and returns unsigned calls in signing order |
cleave_track_swap |
CleaveTrackSwap |
Reports the state of a sent plan or a gas order, and what arrived |
cleave_find_token |
CleaveFindToken |
Finds a token by symbol or address and flags look-alikes |
cleave_get_gas |
CleaveGetGas |
Offers native gas for USDC; with the relay turned on, passes the order the wallet signed to Cleave's relayer |
CleaveToolkit().get_tools() returns all four.
Every input field is the same as the MCP tool's: chain ids are integers (1, 10, 56, 137, 8453, 42161), amounts are decimal strings in token units, and tokens are listed symbols or contract addresses.
Use with an agent
This example also needs langchain and a chat model package:
pip install -U langchain-cleave langchain "langchain[anthropic]"
from langchain.agents import create_agent
from langchain_cleave import CleaveToolkit
agent = create_agent(
model="claude-sonnet-4-6",
tools=CleaveToolkit().get_tools(),
)
agent.invoke(
{
"messages": [
{
"role": "user",
"content": (
"Plan a swap of 25 USDC to WETH on Base for wallet "
"0x00000000000000000000000000000000000A11cE"
),
}
]
}
)
Call a tool directly
from langchain_cleave import CleaveFindToken, CleavePlanSwap
CleaveFindToken().invoke({"chainId": 42161, "query": "USDC"})
plan = CleavePlanSwap().invoke(
{
"fromChainId": 8453,
"sellToken": "USDC",
"buyToken": "WETH",
"amount": "25",
"wallet": "0x00000000000000000000000000000000000A11cE",
}
)
A plan is a dict. Read it in this order:
blockedandnotices: ablocknotice meanscallsis empty.requiresConfirmation: set by awarnnotice or a recipient other than the wallet. Show the plan to the user before signing.route.outcomeKindandreceive: what arrives, the minimum, and the fallback or refund.fees: costs charged by others, such as a bridge fee. Cleave's fees are included in the price. Network gas is not listed.validUntil: sign and sendcallsin order before this unix time.
Before anything is signed
- Do not act on a blocked plan.
- Show the user the warnings, the recipient, the amounts and the slippage,
and get their confirmation before any wallet signs. The
cleave_plan_swapdescription tells the model the same, and not to raise slippage above the default 50 bps without the user's consent. - Never change the recipient or raise slippage to get past a refusal or a warning.
blocked,requiresConfirmationand the notices are advisory. They bind only if your application, or the signer it uses, enforces them.
Signing and tracking
callsare in signing order. Any approval calls come first; the last call is the swap or transfer itself.- Each approval must be mined and succeed before the next call is sent.
- If
validUntilpasses before the last call is sent, plan again and sign the new calls. - Track the mined hash of the last call, the execution transaction, with
cleave_track_swap, passingchainId,txHashand the plan'srequestId. Do not track an approval's hash: a refusal on an approval hash, such asnot_a_cleave_tx, says nothing about the swap. - Track before sending a plan again: a plan still in flight that is sent twice can pay twice.
Finding tokens
cleave_find_token returns up to ten candidates. When a symbol is shared by
many tokens, the one the user means may not be among them: prefer a verified
contract address. A search by symbol or name is limited to 48 characters
after normalization, although the input schema accepts 64.
Gas top-ups
cleave_get_gas takes two calls with separate fields; fields from one call
are refused in the other.
- Offer:
payChainId,chainId,recipient,amount(USDC),walletand optionallyrefundTo. It returns the offer, the fee, the minimum native amount, the deadline, the order and, only whenwalletis given,typedData: the USDC authorization to sign. - Submit:
offer,signatureandwallet. It passes the order the wallet signed to Cleave's relayer, which executes it, and returns arequestIdforcleave_track_swap.
from langchain_cleave import CleaveGetGas
offer = CleaveGetGas().invoke(
{
"payChainId": 8453,
"chainId": 8453,
"recipient": "0x00000000000000000000000000000000000A11cE",
"amount": "5",
"wallet": "0x00000000000000000000000000000000000A11cE",
}
)
The refund address in the order is refundTo if given, else wallet, else
the recipient for an order on one chain. An order paid on another chain
needs wallet or refundTo. Adding wallet only at submission does not
change the refund address of the order already offered. Review both the
recipient and the refund address in order before signing.
| Check | Value |
|---|---|
| Amount | USDC, at most 6 decimals |
| Smallest order | 2 USDC, or 5 USDC when the gas is delivered on Ethereum |
| Largest order | 50 USDC |
| USDC paid on | Ethereum, Arbitrum, Base, Optimism, Polygon |
- An authorization that is never submitted never debits the wallet.
- Once an order is funded, its outcome is whatever
cleave_track_swapreports forg:<payChainId>:<orderHash>. Do not assume a refund because a deadline passed; track until the status shows it. - See x402 gas top-up.
The relay is off by default
The submit call is off by default: the tool returns offers, and a call with
offer or signature comes back as a refusal with code relay_disabled,
without a request. Turn it on in code, not from the agent:
from langchain_cleave import CleaveGetGas, CleaveToolkit
tools = CleaveToolkit(include_gas_relay=True).get_tools()
gas = CleaveGetGas(allow_relay=True)
With the relay on, an agent can keep a person in the loop before each signed
order is sent. HumanInTheLoopMiddleware pauses on that tool; the agent needs
a checkpointer:
from langchain.agents import create_agent
from langchain.agents.middleware import HumanInTheLoopMiddleware
from langgraph.checkpoint.memory import InMemorySaver
agent = create_agent(
model="claude-sonnet-4-6",
tools=CleaveToolkit(include_gas_relay=True).get_tools(),
middleware=[HumanInTheLoopMiddleware(interrupt_on={"cleave_get_gas": True})],
checkpointer=InMemorySaver(),
)
When a submission fails
A submission that times out, loses its connection, or gets a server error or
an unreadable answer may still have been accepted. The tool then returns
outcome_unknown, which is not retryable. When the tool returned the offer
itself, the body carries payChainId, orderHash and trackRequestId.
- Track
g:<payChainId>:<orderHash>withcleave_track_swapbefore signing a new offer. Do not sign a replacement until the original shows its outcome: two signed orders can both be paid. - Cancelling the call, or the agent run, does not cancel an order the relayer already accepted.
- A later
authorization_usedoroffer_expiredrefusal does not prove that an earlier submission failed. Track the order.
Errors
What a tool returns depends on how it is called:
| Call | Success | Refusal |
|---|---|---|
tool.invoke({...}) |
a dict |
a JSON string |
tool.invoke(tool_call) |
a ToolMessage |
a ToolMessage with status="error" |
A refusal from the API carries the API's JSON body, as over MCP:
{"apiVersion", "requestId", "error": {"code", "reason", "message", "retryable", ...}}.
Refusals made by the package itself have only error:
network_error: the request did not complete (retryable).invalid_param: the input breaks the schema, such as an unsupported or boolean chain id, a malformed address or an explicitnull. No request is sent;detailnames each field. Omit an optional field instead of sendingnull.relay_disabledandoutcome_unknown: see Gas top-ups.
To raise instead, pass handle_tool_error=False. The tool then raises
CleaveAPIError, a ToolException with status, code, reason,
retryable and the full body. Pass handle_validation_error=False to raise
a pydantic ValidationError for schema violations.
Configuration
import httpx
from langchain_cleave import CleaveAPIWrapper, CleaveToolkit
api = CleaveAPIWrapper(
base_url="https://api.cleave.fi",
timeout=60.0,
client=httpx.Client(timeout=60.0),
)
tools = CleaveToolkit(api_wrapper=api).get_tools()
base_url: defaults tohttps://api.cleave.fi.timeout: the httpx timeout in seconds, applied to each phase of a request (connect, read, write and pool), 60 by default. It is passed on every request, also with your own client.client,async_client: optionalhttpxclients, for proxies or reuse. The package sets its own authentication on each request: anAuthorizationheader orauthset on your client is never sent.api_key: optional partner key, of the formcq_live_<key>. It is sent asAuthorization: Bearer cq_live_<key>. Keyless use needs none.- When the argument is omitted, the key is read from
CLEAVE_API_KEY. api_key=Nonemeans keyless, even whenCLEAVE_API_KEYis set.- A set key replaces keyless use: every call is checked against that key, and a bad or revoked key fails every call. There is no fallback to keyless.
- When the argument is omitted, the key is read from
Call the API from a server, bot or agent process. Requests that carry an
Origin header, as browsers send, are refused.
Limits
- Without a key, each caller's address can make 40 requests per 10 seconds
and 120 per minute, and all keyless agents share one planning capacity.
When it is busy, the refusal is
rate_limitedwith a shortretryAfterSec. - The token to receive must be on Cleave's token list.
- A cross-chain plan from one token to another refuses
maxSlippageBps: 0. - The schema lists six chains, but a deployment can turn a chain off; a plan
on it is then refused with
unsupported_chain.
Links
- Cleave for agents
- Agent REST API
- OpenAPI
- MCP server:
https://api.cleave.fi/mcp
Terms
Use of the API is subject to Cleave's Terms of Use and Privacy Policy.
License
MIT
Metadata
Release files for langchain-cleave 0.1.0
For a detailed explanation of source distributions (sdists) and built distributions (wheels), please see the package formats documentation.
Source distribution (sdist)
| File | Size | Uploaded | Provenance | Actions |
|---|---|---|---|---|
| langchain_cleave-0.1.0.tar.gz | 21.5 kB | With provenance | Details for langchain_cleave-0.1.0.tar.gz |
Built distribution (wheel)
| File | Interpreter | ABI | Platform | Actions Reset |
|---|---|---|---|---|
| langchain_cleave-0.1.0-py3-none-any.whl | Python 3 | none | any | Details for langchain_cleave-0.1.0-py3-none-any.whl |
Total release size: 36.4 kB
Release files / langchain_cleave-0.1.0.tar.gz
| Download URL | langchain_cleave-0.1.0.tar.gz |
|---|---|
| Size | 21.5 kB |
| Tags | Source |
|
SHA-256 checksum How to use checksums |
3a44604a24f733117a2bd2c20f8e80748ca4da4b9ddf30b83041c53c09bfd8e9
|
|
BLAKE2b-256 checksum How to use checksums |
7af1afbf8430eb123dc2b5e40f6a6de72fce99f32f10df99b1cbc84ebe6fb318
|
| Upload date | |
|
Uploaded using Trusted Publishing? What is trusted publishing? |
Yes |
| Uploaded via |
twine/7.0.0 CPython/3.13.16
|
Provenance
Provenance describes where a file came from. On PyPI, provenance is shared via attestations, which provide a verifiable record of the build or publishing details. View details, limitations and caveats.
PyPI Publish Attestation
PyPI verified that this artifact, at this checksum, originated from the publisher listed below.
Signed by GitHub Actions, verified by PyPI on Nov 2, 2026.
Transparency logRelease files / langchain_cleave-0.1.0-py3-none-any.whl
| Download URL | langchain_cleave-0.1.0-py3-none-any.whl |
|---|---|
| Size | 14.9 kB |
| Tags | Python 3 |
|
SHA-256 checksum How to use checksums |
9320f1d186e8b58bb4a6748378deb9ceb421d4a5fe54110a203e657cd27f9045
|
|
BLAKE2b-256 checksum How to use checksums |
f99dc18dc4da6c8a8d52aa364cbab78ef861ce146bfa0517df979d2dfa01ab41
|
| Upload date | |
|
Uploaded using Trusted Publishing? What is trusted publishing? |
Yes |
| Uploaded via |
twine/7.0.0 CPython/3.13.16
|
Provenance
Provenance describes where a file came from. On PyPI, provenance is shared via attestations, which provide a verifiable record of the build or publishing details. View details, limitations and caveats.
PyPI Publish Attestation
PyPI verified that this artifact, at this checksum, originated from the publisher listed below.
Signed by GitHub Actions, verified by PyPI on Nov 2, 2026.
Transparency logSecurity for langchain-cleave 0.1.0
Provenance
Provenance describes where a file came from. On PyPI, provenance is shared via attestations, which provide a verifiable record of the build or publishing details. View details, limitations and caveats.
Full provenance
All 2 files in this release have been uploaded with provenance metadata.
Attestation for langchain_cleave-0.1.0.tar.gz
PyPI Publish Attestation
PyPI verified that this artifact, at this checksum, originated from the publisher listed below.
Signed by GitHub Actions, verified by PyPI on Nov 2, 2026.
Transparency logA note on package security
PyPI is a repository for distributing packages, not a service for auditing them. While third-party security researchers and our staff do their best to detect and remove malicious packages, PyPI cannot guarantee the quality, security or suitability of any given package.
To learn more about protecting yourself from malicious actors, visit our package security documentation.