langchain-cleave 0.1.0__tar.gz
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- langchain_cleave-0.1.0/.gitignore +10 -0
- langchain_cleave-0.1.0/LICENSE +21 -0
- langchain_cleave-0.1.0/PKG-INFO +337 -0
- langchain_cleave-0.1.0/README.md +309 -0
- langchain_cleave-0.1.0/langchain_cleave/__init__.py +29 -0
- langchain_cleave-0.1.0/langchain_cleave/_api.py +152 -0
- langchain_cleave-0.1.0/langchain_cleave/_descriptions.py +59 -0
- langchain_cleave-0.1.0/langchain_cleave/py.typed +0 -0
- langchain_cleave-0.1.0/langchain_cleave/toolkit.py +27 -0
- langchain_cleave-0.1.0/langchain_cleave/tools.py +350 -0
- langchain_cleave-0.1.0/pyproject.toml +77 -0
- langchain_cleave-0.1.0/tests/__init__.py +0 -0
- langchain_cleave-0.1.0/tests/integration_tests/__init__.py +0 -0
- langchain_cleave-0.1.0/tests/integration_tests/test_compile.py +6 -0
- langchain_cleave-0.1.0/tests/integration_tests/test_standard.py +66 -0
- langchain_cleave-0.1.0/tests/integration_tests/test_tools.py +73 -0
- langchain_cleave-0.1.0/tests/unit_tests/__init__.py +0 -0
- langchain_cleave-0.1.0/tests/unit_tests/backend_tool_texts.json +6 -0
- langchain_cleave-0.1.0/tests/unit_tests/openapi_request_schemas.json +175 -0
- langchain_cleave-0.1.0/tests/unit_tests/test_imports.py +20 -0
- langchain_cleave-0.1.0/tests/unit_tests/test_standard.py +66 -0
- langchain_cleave-0.1.0/tests/unit_tests/test_tools.py +1020 -0
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
MIT License
|
|
2
|
+
|
|
3
|
+
Copyright (c) 2026 Cleave
|
|
4
|
+
|
|
5
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
6
|
+
of this software and associated documentation files (the "Software"), to deal
|
|
7
|
+
in the Software without restriction, including without limitation the rights
|
|
8
|
+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
9
|
+
copies of the Software, and to permit persons to whom the Software is
|
|
10
|
+
furnished to do so, subject to the following conditions:
|
|
11
|
+
|
|
12
|
+
The above copyright notice and this permission notice shall be included in all
|
|
13
|
+
copies or substantial portions of the Software.
|
|
14
|
+
|
|
15
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
16
|
+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
17
|
+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
18
|
+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
19
|
+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
20
|
+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
|
21
|
+
SOFTWARE.
|
|
@@ -0,0 +1,337 @@
|
|
|
1
|
+
Metadata-Version: 2.5
|
|
2
|
+
Name: langchain-cleave
|
|
3
|
+
Version: 0.1.0
|
|
4
|
+
Summary: An integration package connecting Cleave and LangChain
|
|
5
|
+
Project-URL: Homepage, https://cleave.fi/docs/agents
|
|
6
|
+
Project-URL: Documentation, https://cleave.fi/docs/agents/rest
|
|
7
|
+
Project-URL: Repository, https://github.com/cleave-protocol/langchain-cleave
|
|
8
|
+
Project-URL: Issues, https://github.com/cleave-protocol/langchain-cleave/issues
|
|
9
|
+
License-Expression: MIT
|
|
10
|
+
License-File: LICENSE
|
|
11
|
+
Keywords: agents,cleave,cross-chain,dex,langchain,swap
|
|
12
|
+
Classifier: Development Status :: 4 - Beta
|
|
13
|
+
Classifier: Intended Audience :: Developers
|
|
14
|
+
Classifier: Operating System :: OS Independent
|
|
15
|
+
Classifier: Programming Language :: Python :: 3
|
|
16
|
+
Classifier: Programming Language :: Python :: 3.10
|
|
17
|
+
Classifier: Programming Language :: Python :: 3.11
|
|
18
|
+
Classifier: Programming Language :: Python :: 3.12
|
|
19
|
+
Classifier: Programming Language :: Python :: 3.13
|
|
20
|
+
Classifier: Programming Language :: Python :: 3.14
|
|
21
|
+
Classifier: Topic :: Scientific/Engineering :: Artificial Intelligence
|
|
22
|
+
Classifier: Typing :: Typed
|
|
23
|
+
Requires-Python: <4.0.0,>=3.10.0
|
|
24
|
+
Requires-Dist: httpx<1.0.0,>=0.27.0
|
|
25
|
+
Requires-Dist: langchain-core<2.0.0,>=1.0.0
|
|
26
|
+
Requires-Dist: pydantic<3.0.0,>=2.7.4
|
|
27
|
+
Description-Content-Type: text/markdown
|
|
28
|
+
|
|
29
|
+
# langchain-cleave
|
|
30
|
+
|
|
31
|
+
LangChain tools for [Cleave](https://cleave.fi), a DEX aggregator on Ethereum,
|
|
32
|
+
Arbitrum, Base, BNB Chain, Polygon and Optimism, with split routing and
|
|
33
|
+
one-step cross-chain swaps.
|
|
34
|
+
|
|
35
|
+
The package gives a LangChain agent the four tools of Cleave's MCP server,
|
|
36
|
+
with the same names, inputs and answers. They call the keyless
|
|
37
|
+
[Agent REST API](https://cleave.fi/docs/agents/rest). No API key is needed.
|
|
38
|
+
|
|
39
|
+
The tools return unsigned calls for the user's wallet to sign and send. They
|
|
40
|
+
never take a private key. They sign nothing; the only thing they send is a gas
|
|
41
|
+
order the wallet has signed, and only when the gas relay is turned on (it is
|
|
42
|
+
off by default; see [Gas top-ups](#gas-top-ups)).
|
|
43
|
+
|
|
44
|
+
## Installation
|
|
45
|
+
|
|
46
|
+
```bash
|
|
47
|
+
pip install -U langchain-cleave
|
|
48
|
+
```
|
|
49
|
+
|
|
50
|
+
```bash
|
|
51
|
+
uv add langchain-cleave
|
|
52
|
+
```
|
|
53
|
+
|
|
54
|
+
Python 3.10 or later.
|
|
55
|
+
|
|
56
|
+
## Tools
|
|
57
|
+
|
|
58
|
+
| Tool | Class | What it does |
|
|
59
|
+
| --- | --- | --- |
|
|
60
|
+
| `cleave_plan_swap` | `CleavePlanSwap` | Plans a swap on one chain, a cross-chain swap or a bridge, and returns unsigned calls in signing order |
|
|
61
|
+
| `cleave_track_swap` | `CleaveTrackSwap` | Reports the state of a sent plan or a gas order, and what arrived |
|
|
62
|
+
| `cleave_find_token` | `CleaveFindToken` | Finds a token by symbol or address and flags look-alikes |
|
|
63
|
+
| `cleave_get_gas` | `CleaveGetGas` | Offers native gas for USDC; with the relay turned on, passes the order the wallet signed to Cleave's relayer |
|
|
64
|
+
|
|
65
|
+
`CleaveToolkit().get_tools()` returns all four.
|
|
66
|
+
|
|
67
|
+
Every input field is the same as the MCP tool's: chain ids are integers
|
|
68
|
+
(1, 10, 56, 137, 8453, 42161), amounts are decimal strings in token units,
|
|
69
|
+
and tokens are listed symbols or contract addresses.
|
|
70
|
+
|
|
71
|
+
## Use with an agent
|
|
72
|
+
|
|
73
|
+
This example also needs `langchain` and a chat model package:
|
|
74
|
+
|
|
75
|
+
```bash
|
|
76
|
+
pip install -U langchain-cleave langchain "langchain[anthropic]"
|
|
77
|
+
```
|
|
78
|
+
|
|
79
|
+
```python
|
|
80
|
+
from langchain.agents import create_agent
|
|
81
|
+
from langchain_cleave import CleaveToolkit
|
|
82
|
+
|
|
83
|
+
agent = create_agent(
|
|
84
|
+
model="claude-sonnet-4-6",
|
|
85
|
+
tools=CleaveToolkit().get_tools(),
|
|
86
|
+
)
|
|
87
|
+
|
|
88
|
+
agent.invoke(
|
|
89
|
+
{
|
|
90
|
+
"messages": [
|
|
91
|
+
{
|
|
92
|
+
"role": "user",
|
|
93
|
+
"content": (
|
|
94
|
+
"Plan a swap of 25 USDC to WETH on Base for wallet "
|
|
95
|
+
"0x00000000000000000000000000000000000A11cE"
|
|
96
|
+
),
|
|
97
|
+
}
|
|
98
|
+
]
|
|
99
|
+
}
|
|
100
|
+
)
|
|
101
|
+
```
|
|
102
|
+
|
|
103
|
+
## Call a tool directly
|
|
104
|
+
|
|
105
|
+
```python
|
|
106
|
+
from langchain_cleave import CleaveFindToken, CleavePlanSwap
|
|
107
|
+
|
|
108
|
+
CleaveFindToken().invoke({"chainId": 42161, "query": "USDC"})
|
|
109
|
+
|
|
110
|
+
plan = CleavePlanSwap().invoke(
|
|
111
|
+
{
|
|
112
|
+
"fromChainId": 8453,
|
|
113
|
+
"sellToken": "USDC",
|
|
114
|
+
"buyToken": "WETH",
|
|
115
|
+
"amount": "25",
|
|
116
|
+
"wallet": "0x00000000000000000000000000000000000A11cE",
|
|
117
|
+
}
|
|
118
|
+
)
|
|
119
|
+
```
|
|
120
|
+
|
|
121
|
+
A plan is a dict. Read it in this order:
|
|
122
|
+
|
|
123
|
+
1. `blocked` and `notices`: a `block` notice means `calls` is empty.
|
|
124
|
+
2. `requiresConfirmation`: set by a `warn` notice or a recipient other than
|
|
125
|
+
the wallet. Show the plan to the user before signing.
|
|
126
|
+
3. `route.outcomeKind` and `receive`: what arrives, the minimum, and the
|
|
127
|
+
fallback or refund.
|
|
128
|
+
4. `fees`: costs charged by others, such as a bridge fee. Cleave's fees are
|
|
129
|
+
included in the price. Network gas is not listed.
|
|
130
|
+
5. `validUntil`: sign and send `calls` in order before this unix time.
|
|
131
|
+
|
|
132
|
+
### Before anything is signed
|
|
133
|
+
|
|
134
|
+
- Do not act on a blocked plan.
|
|
135
|
+
- Show the user the warnings, the recipient, the amounts and the slippage,
|
|
136
|
+
and get their confirmation before any wallet signs. The `cleave_plan_swap`
|
|
137
|
+
description tells the model the same, and not to raise slippage above the
|
|
138
|
+
default 50 bps without the user's consent.
|
|
139
|
+
- Never change the recipient or raise slippage to get past a refusal or a
|
|
140
|
+
warning.
|
|
141
|
+
- `blocked`, `requiresConfirmation` and the notices are advisory. They bind
|
|
142
|
+
only if your application, or the signer it uses, enforces them.
|
|
143
|
+
|
|
144
|
+
### Signing and tracking
|
|
145
|
+
|
|
146
|
+
- `calls` are in signing order. Any approval calls come first; the last call
|
|
147
|
+
is the swap or transfer itself.
|
|
148
|
+
- Each approval must be mined and succeed before the next call is sent.
|
|
149
|
+
- If `validUntil` passes before the last call is sent, plan again and sign
|
|
150
|
+
the new calls.
|
|
151
|
+
- Track the mined hash of the last call, the execution transaction, with
|
|
152
|
+
`cleave_track_swap`, passing `chainId`, `txHash` and the plan's `requestId`.
|
|
153
|
+
Do not track an approval's hash: a refusal on an approval hash, such as
|
|
154
|
+
`not_a_cleave_tx`, says nothing about the swap.
|
|
155
|
+
- Track before sending a plan again: a plan still in flight that is sent
|
|
156
|
+
twice can pay twice.
|
|
157
|
+
|
|
158
|
+
### Finding tokens
|
|
159
|
+
|
|
160
|
+
`cleave_find_token` returns up to ten candidates. When a symbol is shared by
|
|
161
|
+
many tokens, the one the user means may not be among them: prefer a verified
|
|
162
|
+
contract address. A search by symbol or name is limited to 48 characters
|
|
163
|
+
after normalization, although the input schema accepts 64.
|
|
164
|
+
|
|
165
|
+
## Gas top-ups
|
|
166
|
+
|
|
167
|
+
`cleave_get_gas` takes two calls with separate fields; fields from one call
|
|
168
|
+
are refused in the other.
|
|
169
|
+
|
|
170
|
+
1. Offer: `payChainId`, `chainId`, `recipient`, `amount` (USDC), `wallet`
|
|
171
|
+
and optionally `refundTo`. It returns the offer, the fee, the minimum
|
|
172
|
+
native amount, the deadline, the order and, only when `wallet` is given,
|
|
173
|
+
`typedData`: the USDC authorization to sign.
|
|
174
|
+
2. Submit: `offer`, `signature` and `wallet`. It passes the order the wallet
|
|
175
|
+
signed to Cleave's relayer, which executes it, and returns a `requestId`
|
|
176
|
+
for `cleave_track_swap`.
|
|
177
|
+
|
|
178
|
+
```python
|
|
179
|
+
from langchain_cleave import CleaveGetGas
|
|
180
|
+
|
|
181
|
+
offer = CleaveGetGas().invoke(
|
|
182
|
+
{
|
|
183
|
+
"payChainId": 8453,
|
|
184
|
+
"chainId": 8453,
|
|
185
|
+
"recipient": "0x00000000000000000000000000000000000A11cE",
|
|
186
|
+
"amount": "5",
|
|
187
|
+
"wallet": "0x00000000000000000000000000000000000A11cE",
|
|
188
|
+
}
|
|
189
|
+
)
|
|
190
|
+
```
|
|
191
|
+
|
|
192
|
+
The refund address in the order is `refundTo` if given, else `wallet`, else
|
|
193
|
+
the `recipient` for an order on one chain. An order paid on another chain
|
|
194
|
+
needs `wallet` or `refundTo`. Adding `wallet` only at submission does not
|
|
195
|
+
change the refund address of the order already offered. Review both the
|
|
196
|
+
recipient and the refund address in `order` before signing.
|
|
197
|
+
|
|
198
|
+
| Check | Value |
|
|
199
|
+
| --- | --- |
|
|
200
|
+
| Amount | USDC, at most 6 decimals |
|
|
201
|
+
| Smallest order | 2 USDC, or 5 USDC when the gas is delivered on Ethereum |
|
|
202
|
+
| Largest order | 50 USDC |
|
|
203
|
+
| USDC paid on | Ethereum, Arbitrum, Base, Optimism, Polygon |
|
|
204
|
+
|
|
205
|
+
- An authorization that is never submitted never debits the wallet.
|
|
206
|
+
- Once an order is funded, its outcome is whatever `cleave_track_swap`
|
|
207
|
+
reports for `g:<payChainId>:<orderHash>`. Do not assume a refund because a
|
|
208
|
+
deadline passed; track until the status shows it.
|
|
209
|
+
- See [x402 gas top-up](https://cleave.fi/docs/agents/gas).
|
|
210
|
+
|
|
211
|
+
### The relay is off by default
|
|
212
|
+
|
|
213
|
+
The submit call is off by default: the tool returns offers, and a call with
|
|
214
|
+
`offer` or `signature` comes back as a refusal with code `relay_disabled`,
|
|
215
|
+
without a request. Turn it on in code, not from the agent:
|
|
216
|
+
|
|
217
|
+
```python
|
|
218
|
+
from langchain_cleave import CleaveGetGas, CleaveToolkit
|
|
219
|
+
|
|
220
|
+
tools = CleaveToolkit(include_gas_relay=True).get_tools()
|
|
221
|
+
gas = CleaveGetGas(allow_relay=True)
|
|
222
|
+
```
|
|
223
|
+
|
|
224
|
+
With the relay on, an agent can keep a person in the loop before each signed
|
|
225
|
+
order is sent. `HumanInTheLoopMiddleware` pauses on that tool; the agent needs
|
|
226
|
+
a checkpointer:
|
|
227
|
+
|
|
228
|
+
```python
|
|
229
|
+
from langchain.agents import create_agent
|
|
230
|
+
from langchain.agents.middleware import HumanInTheLoopMiddleware
|
|
231
|
+
from langgraph.checkpoint.memory import InMemorySaver
|
|
232
|
+
|
|
233
|
+
agent = create_agent(
|
|
234
|
+
model="claude-sonnet-4-6",
|
|
235
|
+
tools=CleaveToolkit(include_gas_relay=True).get_tools(),
|
|
236
|
+
middleware=[HumanInTheLoopMiddleware(interrupt_on={"cleave_get_gas": True})],
|
|
237
|
+
checkpointer=InMemorySaver(),
|
|
238
|
+
)
|
|
239
|
+
```
|
|
240
|
+
|
|
241
|
+
### When a submission fails
|
|
242
|
+
|
|
243
|
+
A submission that times out, loses its connection, or gets a server error or
|
|
244
|
+
an unreadable answer may still have been accepted. The tool then returns
|
|
245
|
+
`outcome_unknown`, which is not retryable. When the tool returned the offer
|
|
246
|
+
itself, the body carries `payChainId`, `orderHash` and `trackRequestId`.
|
|
247
|
+
|
|
248
|
+
- Track `g:<payChainId>:<orderHash>` with `cleave_track_swap` before signing
|
|
249
|
+
a new offer. Do not sign a replacement until the original shows its
|
|
250
|
+
outcome: two signed orders can both be paid.
|
|
251
|
+
- Cancelling the call, or the agent run, does not cancel an order the relayer
|
|
252
|
+
already accepted.
|
|
253
|
+
- A later `authorization_used` or `offer_expired` refusal does not prove that
|
|
254
|
+
an earlier submission failed. Track the order.
|
|
255
|
+
|
|
256
|
+
## Errors
|
|
257
|
+
|
|
258
|
+
What a tool returns depends on how it is called:
|
|
259
|
+
|
|
260
|
+
| Call | Success | Refusal |
|
|
261
|
+
| --- | --- | --- |
|
|
262
|
+
| `tool.invoke({...})` | a `dict` | a JSON string |
|
|
263
|
+
| `tool.invoke(tool_call)` | a `ToolMessage` | a `ToolMessage` with `status="error"` |
|
|
264
|
+
|
|
265
|
+
A refusal from the API carries the API's JSON body, as over MCP:
|
|
266
|
+
`{"apiVersion", "requestId", "error": {"code", "reason", "message", "retryable", ...}}`.
|
|
267
|
+
Refusals made by the package itself have only `error`:
|
|
268
|
+
|
|
269
|
+
- `network_error`: the request did not complete (retryable).
|
|
270
|
+
- `invalid_param`: the input breaks the schema, such as an unsupported or
|
|
271
|
+
boolean chain id, a malformed address or an explicit `null`. No request is
|
|
272
|
+
sent; `detail` names each field. Omit an optional field instead of sending
|
|
273
|
+
`null`.
|
|
274
|
+
- `relay_disabled` and `outcome_unknown`: see [Gas top-ups](#gas-top-ups).
|
|
275
|
+
|
|
276
|
+
To raise instead, pass `handle_tool_error=False`. The tool then raises
|
|
277
|
+
`CleaveAPIError`, a `ToolException` with `status`, `code`, `reason`,
|
|
278
|
+
`retryable` and the full `body`. Pass `handle_validation_error=False` to raise
|
|
279
|
+
a pydantic `ValidationError` for schema violations.
|
|
280
|
+
## Configuration
|
|
281
|
+
|
|
282
|
+
```python
|
|
283
|
+
import httpx
|
|
284
|
+
from langchain_cleave import CleaveAPIWrapper, CleaveToolkit
|
|
285
|
+
|
|
286
|
+
api = CleaveAPIWrapper(
|
|
287
|
+
base_url="https://api.cleave.fi",
|
|
288
|
+
timeout=60.0,
|
|
289
|
+
client=httpx.Client(timeout=60.0),
|
|
290
|
+
)
|
|
291
|
+
tools = CleaveToolkit(api_wrapper=api).get_tools()
|
|
292
|
+
```
|
|
293
|
+
|
|
294
|
+
- `base_url`: defaults to `https://api.cleave.fi`.
|
|
295
|
+
- `timeout`: the httpx timeout in seconds, applied to each phase of a
|
|
296
|
+
request (connect, read, write and pool), 60 by default. It is passed on
|
|
297
|
+
every request, also with your own client.
|
|
298
|
+
- `client`, `async_client`: optional `httpx` clients, for proxies or reuse.
|
|
299
|
+
The package sets its own authentication on each request: an
|
|
300
|
+
`Authorization` header or `auth` set on your client is never sent.
|
|
301
|
+
- `api_key`: optional partner key, of the form `cq_live_<key>`. It is sent as
|
|
302
|
+
`Authorization: Bearer cq_live_<key>`. Keyless use needs none.
|
|
303
|
+
- When the argument is omitted, the key is read from `CLEAVE_API_KEY`.
|
|
304
|
+
- `api_key=None` means keyless, even when `CLEAVE_API_KEY` is set.
|
|
305
|
+
- A set key replaces keyless use: every call is checked against that key,
|
|
306
|
+
and a bad or revoked key fails every call. There is no fallback to
|
|
307
|
+
keyless.
|
|
308
|
+
|
|
309
|
+
Call the API from a server, bot or agent process. Requests that carry an
|
|
310
|
+
`Origin` header, as browsers send, are refused.
|
|
311
|
+
|
|
312
|
+
## Limits
|
|
313
|
+
|
|
314
|
+
- Without a key, each caller's address can make 40 requests per 10 seconds
|
|
315
|
+
and 120 per minute, and all keyless agents share one planning capacity.
|
|
316
|
+
When it is busy, the refusal is `rate_limited` with a short
|
|
317
|
+
`retryAfterSec`.
|
|
318
|
+
- The token to receive must be on Cleave's token list.
|
|
319
|
+
- A cross-chain plan from one token to another refuses `maxSlippageBps: 0`.
|
|
320
|
+
- The schema lists six chains, but a deployment can turn a chain off; a plan
|
|
321
|
+
on it is then refused with `unsupported_chain`.
|
|
322
|
+
|
|
323
|
+
## Links
|
|
324
|
+
|
|
325
|
+
- [Cleave for agents](https://cleave.fi/docs/agents)
|
|
326
|
+
- [Agent REST API](https://cleave.fi/docs/agents/rest)
|
|
327
|
+
- [OpenAPI](https://api.cleave.fi/agent/v1/openapi.json)
|
|
328
|
+
- [MCP server](https://cleave.fi/docs/agents/mcp): `https://api.cleave.fi/mcp`
|
|
329
|
+
|
|
330
|
+
## Terms
|
|
331
|
+
|
|
332
|
+
Use of the API is subject to Cleave's [Terms of Use](https://cleave.fi/terms)
|
|
333
|
+
and [Privacy Policy](https://cleave.fi/privacy).
|
|
334
|
+
|
|
335
|
+
## License
|
|
336
|
+
|
|
337
|
+
MIT
|
|
@@ -0,0 +1,309 @@
|
|
|
1
|
+
# langchain-cleave
|
|
2
|
+
|
|
3
|
+
LangChain tools for [Cleave](https://cleave.fi), a DEX aggregator on Ethereum,
|
|
4
|
+
Arbitrum, Base, BNB Chain, Polygon and Optimism, with split routing and
|
|
5
|
+
one-step cross-chain swaps.
|
|
6
|
+
|
|
7
|
+
The package gives a LangChain agent the four tools of Cleave's MCP server,
|
|
8
|
+
with the same names, inputs and answers. They call the keyless
|
|
9
|
+
[Agent REST API](https://cleave.fi/docs/agents/rest). No API key is needed.
|
|
10
|
+
|
|
11
|
+
The tools return unsigned calls for the user's wallet to sign and send. They
|
|
12
|
+
never take a private key. They sign nothing; the only thing they send is a gas
|
|
13
|
+
order the wallet has signed, and only when the gas relay is turned on (it is
|
|
14
|
+
off by default; see [Gas top-ups](#gas-top-ups)).
|
|
15
|
+
|
|
16
|
+
## Installation
|
|
17
|
+
|
|
18
|
+
```bash
|
|
19
|
+
pip install -U langchain-cleave
|
|
20
|
+
```
|
|
21
|
+
|
|
22
|
+
```bash
|
|
23
|
+
uv add langchain-cleave
|
|
24
|
+
```
|
|
25
|
+
|
|
26
|
+
Python 3.10 or later.
|
|
27
|
+
|
|
28
|
+
## Tools
|
|
29
|
+
|
|
30
|
+
| Tool | Class | What it does |
|
|
31
|
+
| --- | --- | --- |
|
|
32
|
+
| `cleave_plan_swap` | `CleavePlanSwap` | Plans a swap on one chain, a cross-chain swap or a bridge, and returns unsigned calls in signing order |
|
|
33
|
+
| `cleave_track_swap` | `CleaveTrackSwap` | Reports the state of a sent plan or a gas order, and what arrived |
|
|
34
|
+
| `cleave_find_token` | `CleaveFindToken` | Finds a token by symbol or address and flags look-alikes |
|
|
35
|
+
| `cleave_get_gas` | `CleaveGetGas` | Offers native gas for USDC; with the relay turned on, passes the order the wallet signed to Cleave's relayer |
|
|
36
|
+
|
|
37
|
+
`CleaveToolkit().get_tools()` returns all four.
|
|
38
|
+
|
|
39
|
+
Every input field is the same as the MCP tool's: chain ids are integers
|
|
40
|
+
(1, 10, 56, 137, 8453, 42161), amounts are decimal strings in token units,
|
|
41
|
+
and tokens are listed symbols or contract addresses.
|
|
42
|
+
|
|
43
|
+
## Use with an agent
|
|
44
|
+
|
|
45
|
+
This example also needs `langchain` and a chat model package:
|
|
46
|
+
|
|
47
|
+
```bash
|
|
48
|
+
pip install -U langchain-cleave langchain "langchain[anthropic]"
|
|
49
|
+
```
|
|
50
|
+
|
|
51
|
+
```python
|
|
52
|
+
from langchain.agents import create_agent
|
|
53
|
+
from langchain_cleave import CleaveToolkit
|
|
54
|
+
|
|
55
|
+
agent = create_agent(
|
|
56
|
+
model="claude-sonnet-4-6",
|
|
57
|
+
tools=CleaveToolkit().get_tools(),
|
|
58
|
+
)
|
|
59
|
+
|
|
60
|
+
agent.invoke(
|
|
61
|
+
{
|
|
62
|
+
"messages": [
|
|
63
|
+
{
|
|
64
|
+
"role": "user",
|
|
65
|
+
"content": (
|
|
66
|
+
"Plan a swap of 25 USDC to WETH on Base for wallet "
|
|
67
|
+
"0x00000000000000000000000000000000000A11cE"
|
|
68
|
+
),
|
|
69
|
+
}
|
|
70
|
+
]
|
|
71
|
+
}
|
|
72
|
+
)
|
|
73
|
+
```
|
|
74
|
+
|
|
75
|
+
## Call a tool directly
|
|
76
|
+
|
|
77
|
+
```python
|
|
78
|
+
from langchain_cleave import CleaveFindToken, CleavePlanSwap
|
|
79
|
+
|
|
80
|
+
CleaveFindToken().invoke({"chainId": 42161, "query": "USDC"})
|
|
81
|
+
|
|
82
|
+
plan = CleavePlanSwap().invoke(
|
|
83
|
+
{
|
|
84
|
+
"fromChainId": 8453,
|
|
85
|
+
"sellToken": "USDC",
|
|
86
|
+
"buyToken": "WETH",
|
|
87
|
+
"amount": "25",
|
|
88
|
+
"wallet": "0x00000000000000000000000000000000000A11cE",
|
|
89
|
+
}
|
|
90
|
+
)
|
|
91
|
+
```
|
|
92
|
+
|
|
93
|
+
A plan is a dict. Read it in this order:
|
|
94
|
+
|
|
95
|
+
1. `blocked` and `notices`: a `block` notice means `calls` is empty.
|
|
96
|
+
2. `requiresConfirmation`: set by a `warn` notice or a recipient other than
|
|
97
|
+
the wallet. Show the plan to the user before signing.
|
|
98
|
+
3. `route.outcomeKind` and `receive`: what arrives, the minimum, and the
|
|
99
|
+
fallback or refund.
|
|
100
|
+
4. `fees`: costs charged by others, such as a bridge fee. Cleave's fees are
|
|
101
|
+
included in the price. Network gas is not listed.
|
|
102
|
+
5. `validUntil`: sign and send `calls` in order before this unix time.
|
|
103
|
+
|
|
104
|
+
### Before anything is signed
|
|
105
|
+
|
|
106
|
+
- Do not act on a blocked plan.
|
|
107
|
+
- Show the user the warnings, the recipient, the amounts and the slippage,
|
|
108
|
+
and get their confirmation before any wallet signs. The `cleave_plan_swap`
|
|
109
|
+
description tells the model the same, and not to raise slippage above the
|
|
110
|
+
default 50 bps without the user's consent.
|
|
111
|
+
- Never change the recipient or raise slippage to get past a refusal or a
|
|
112
|
+
warning.
|
|
113
|
+
- `blocked`, `requiresConfirmation` and the notices are advisory. They bind
|
|
114
|
+
only if your application, or the signer it uses, enforces them.
|
|
115
|
+
|
|
116
|
+
### Signing and tracking
|
|
117
|
+
|
|
118
|
+
- `calls` are in signing order. Any approval calls come first; the last call
|
|
119
|
+
is the swap or transfer itself.
|
|
120
|
+
- Each approval must be mined and succeed before the next call is sent.
|
|
121
|
+
- If `validUntil` passes before the last call is sent, plan again and sign
|
|
122
|
+
the new calls.
|
|
123
|
+
- Track the mined hash of the last call, the execution transaction, with
|
|
124
|
+
`cleave_track_swap`, passing `chainId`, `txHash` and the plan's `requestId`.
|
|
125
|
+
Do not track an approval's hash: a refusal on an approval hash, such as
|
|
126
|
+
`not_a_cleave_tx`, says nothing about the swap.
|
|
127
|
+
- Track before sending a plan again: a plan still in flight that is sent
|
|
128
|
+
twice can pay twice.
|
|
129
|
+
|
|
130
|
+
### Finding tokens
|
|
131
|
+
|
|
132
|
+
`cleave_find_token` returns up to ten candidates. When a symbol is shared by
|
|
133
|
+
many tokens, the one the user means may not be among them: prefer a verified
|
|
134
|
+
contract address. A search by symbol or name is limited to 48 characters
|
|
135
|
+
after normalization, although the input schema accepts 64.
|
|
136
|
+
|
|
137
|
+
## Gas top-ups
|
|
138
|
+
|
|
139
|
+
`cleave_get_gas` takes two calls with separate fields; fields from one call
|
|
140
|
+
are refused in the other.
|
|
141
|
+
|
|
142
|
+
1. Offer: `payChainId`, `chainId`, `recipient`, `amount` (USDC), `wallet`
|
|
143
|
+
and optionally `refundTo`. It returns the offer, the fee, the minimum
|
|
144
|
+
native amount, the deadline, the order and, only when `wallet` is given,
|
|
145
|
+
`typedData`: the USDC authorization to sign.
|
|
146
|
+
2. Submit: `offer`, `signature` and `wallet`. It passes the order the wallet
|
|
147
|
+
signed to Cleave's relayer, which executes it, and returns a `requestId`
|
|
148
|
+
for `cleave_track_swap`.
|
|
149
|
+
|
|
150
|
+
```python
|
|
151
|
+
from langchain_cleave import CleaveGetGas
|
|
152
|
+
|
|
153
|
+
offer = CleaveGetGas().invoke(
|
|
154
|
+
{
|
|
155
|
+
"payChainId": 8453,
|
|
156
|
+
"chainId": 8453,
|
|
157
|
+
"recipient": "0x00000000000000000000000000000000000A11cE",
|
|
158
|
+
"amount": "5",
|
|
159
|
+
"wallet": "0x00000000000000000000000000000000000A11cE",
|
|
160
|
+
}
|
|
161
|
+
)
|
|
162
|
+
```
|
|
163
|
+
|
|
164
|
+
The refund address in the order is `refundTo` if given, else `wallet`, else
|
|
165
|
+
the `recipient` for an order on one chain. An order paid on another chain
|
|
166
|
+
needs `wallet` or `refundTo`. Adding `wallet` only at submission does not
|
|
167
|
+
change the refund address of the order already offered. Review both the
|
|
168
|
+
recipient and the refund address in `order` before signing.
|
|
169
|
+
|
|
170
|
+
| Check | Value |
|
|
171
|
+
| --- | --- |
|
|
172
|
+
| Amount | USDC, at most 6 decimals |
|
|
173
|
+
| Smallest order | 2 USDC, or 5 USDC when the gas is delivered on Ethereum |
|
|
174
|
+
| Largest order | 50 USDC |
|
|
175
|
+
| USDC paid on | Ethereum, Arbitrum, Base, Optimism, Polygon |
|
|
176
|
+
|
|
177
|
+
- An authorization that is never submitted never debits the wallet.
|
|
178
|
+
- Once an order is funded, its outcome is whatever `cleave_track_swap`
|
|
179
|
+
reports for `g:<payChainId>:<orderHash>`. Do not assume a refund because a
|
|
180
|
+
deadline passed; track until the status shows it.
|
|
181
|
+
- See [x402 gas top-up](https://cleave.fi/docs/agents/gas).
|
|
182
|
+
|
|
183
|
+
### The relay is off by default
|
|
184
|
+
|
|
185
|
+
The submit call is off by default: the tool returns offers, and a call with
|
|
186
|
+
`offer` or `signature` comes back as a refusal with code `relay_disabled`,
|
|
187
|
+
without a request. Turn it on in code, not from the agent:
|
|
188
|
+
|
|
189
|
+
```python
|
|
190
|
+
from langchain_cleave import CleaveGetGas, CleaveToolkit
|
|
191
|
+
|
|
192
|
+
tools = CleaveToolkit(include_gas_relay=True).get_tools()
|
|
193
|
+
gas = CleaveGetGas(allow_relay=True)
|
|
194
|
+
```
|
|
195
|
+
|
|
196
|
+
With the relay on, an agent can keep a person in the loop before each signed
|
|
197
|
+
order is sent. `HumanInTheLoopMiddleware` pauses on that tool; the agent needs
|
|
198
|
+
a checkpointer:
|
|
199
|
+
|
|
200
|
+
```python
|
|
201
|
+
from langchain.agents import create_agent
|
|
202
|
+
from langchain.agents.middleware import HumanInTheLoopMiddleware
|
|
203
|
+
from langgraph.checkpoint.memory import InMemorySaver
|
|
204
|
+
|
|
205
|
+
agent = create_agent(
|
|
206
|
+
model="claude-sonnet-4-6",
|
|
207
|
+
tools=CleaveToolkit(include_gas_relay=True).get_tools(),
|
|
208
|
+
middleware=[HumanInTheLoopMiddleware(interrupt_on={"cleave_get_gas": True})],
|
|
209
|
+
checkpointer=InMemorySaver(),
|
|
210
|
+
)
|
|
211
|
+
```
|
|
212
|
+
|
|
213
|
+
### When a submission fails
|
|
214
|
+
|
|
215
|
+
A submission that times out, loses its connection, or gets a server error or
|
|
216
|
+
an unreadable answer may still have been accepted. The tool then returns
|
|
217
|
+
`outcome_unknown`, which is not retryable. When the tool returned the offer
|
|
218
|
+
itself, the body carries `payChainId`, `orderHash` and `trackRequestId`.
|
|
219
|
+
|
|
220
|
+
- Track `g:<payChainId>:<orderHash>` with `cleave_track_swap` before signing
|
|
221
|
+
a new offer. Do not sign a replacement until the original shows its
|
|
222
|
+
outcome: two signed orders can both be paid.
|
|
223
|
+
- Cancelling the call, or the agent run, does not cancel an order the relayer
|
|
224
|
+
already accepted.
|
|
225
|
+
- A later `authorization_used` or `offer_expired` refusal does not prove that
|
|
226
|
+
an earlier submission failed. Track the order.
|
|
227
|
+
|
|
228
|
+
## Errors
|
|
229
|
+
|
|
230
|
+
What a tool returns depends on how it is called:
|
|
231
|
+
|
|
232
|
+
| Call | Success | Refusal |
|
|
233
|
+
| --- | --- | --- |
|
|
234
|
+
| `tool.invoke({...})` | a `dict` | a JSON string |
|
|
235
|
+
| `tool.invoke(tool_call)` | a `ToolMessage` | a `ToolMessage` with `status="error"` |
|
|
236
|
+
|
|
237
|
+
A refusal from the API carries the API's JSON body, as over MCP:
|
|
238
|
+
`{"apiVersion", "requestId", "error": {"code", "reason", "message", "retryable", ...}}`.
|
|
239
|
+
Refusals made by the package itself have only `error`:
|
|
240
|
+
|
|
241
|
+
- `network_error`: the request did not complete (retryable).
|
|
242
|
+
- `invalid_param`: the input breaks the schema, such as an unsupported or
|
|
243
|
+
boolean chain id, a malformed address or an explicit `null`. No request is
|
|
244
|
+
sent; `detail` names each field. Omit an optional field instead of sending
|
|
245
|
+
`null`.
|
|
246
|
+
- `relay_disabled` and `outcome_unknown`: see [Gas top-ups](#gas-top-ups).
|
|
247
|
+
|
|
248
|
+
To raise instead, pass `handle_tool_error=False`. The tool then raises
|
|
249
|
+
`CleaveAPIError`, a `ToolException` with `status`, `code`, `reason`,
|
|
250
|
+
`retryable` and the full `body`. Pass `handle_validation_error=False` to raise
|
|
251
|
+
a pydantic `ValidationError` for schema violations.
|
|
252
|
+
## Configuration
|
|
253
|
+
|
|
254
|
+
```python
|
|
255
|
+
import httpx
|
|
256
|
+
from langchain_cleave import CleaveAPIWrapper, CleaveToolkit
|
|
257
|
+
|
|
258
|
+
api = CleaveAPIWrapper(
|
|
259
|
+
base_url="https://api.cleave.fi",
|
|
260
|
+
timeout=60.0,
|
|
261
|
+
client=httpx.Client(timeout=60.0),
|
|
262
|
+
)
|
|
263
|
+
tools = CleaveToolkit(api_wrapper=api).get_tools()
|
|
264
|
+
```
|
|
265
|
+
|
|
266
|
+
- `base_url`: defaults to `https://api.cleave.fi`.
|
|
267
|
+
- `timeout`: the httpx timeout in seconds, applied to each phase of a
|
|
268
|
+
request (connect, read, write and pool), 60 by default. It is passed on
|
|
269
|
+
every request, also with your own client.
|
|
270
|
+
- `client`, `async_client`: optional `httpx` clients, for proxies or reuse.
|
|
271
|
+
The package sets its own authentication on each request: an
|
|
272
|
+
`Authorization` header or `auth` set on your client is never sent.
|
|
273
|
+
- `api_key`: optional partner key, of the form `cq_live_<key>`. It is sent as
|
|
274
|
+
`Authorization: Bearer cq_live_<key>`. Keyless use needs none.
|
|
275
|
+
- When the argument is omitted, the key is read from `CLEAVE_API_KEY`.
|
|
276
|
+
- `api_key=None` means keyless, even when `CLEAVE_API_KEY` is set.
|
|
277
|
+
- A set key replaces keyless use: every call is checked against that key,
|
|
278
|
+
and a bad or revoked key fails every call. There is no fallback to
|
|
279
|
+
keyless.
|
|
280
|
+
|
|
281
|
+
Call the API from a server, bot or agent process. Requests that carry an
|
|
282
|
+
`Origin` header, as browsers send, are refused.
|
|
283
|
+
|
|
284
|
+
## Limits
|
|
285
|
+
|
|
286
|
+
- Without a key, each caller's address can make 40 requests per 10 seconds
|
|
287
|
+
and 120 per minute, and all keyless agents share one planning capacity.
|
|
288
|
+
When it is busy, the refusal is `rate_limited` with a short
|
|
289
|
+
`retryAfterSec`.
|
|
290
|
+
- The token to receive must be on Cleave's token list.
|
|
291
|
+
- A cross-chain plan from one token to another refuses `maxSlippageBps: 0`.
|
|
292
|
+
- The schema lists six chains, but a deployment can turn a chain off; a plan
|
|
293
|
+
on it is then refused with `unsupported_chain`.
|
|
294
|
+
|
|
295
|
+
## Links
|
|
296
|
+
|
|
297
|
+
- [Cleave for agents](https://cleave.fi/docs/agents)
|
|
298
|
+
- [Agent REST API](https://cleave.fi/docs/agents/rest)
|
|
299
|
+
- [OpenAPI](https://api.cleave.fi/agent/v1/openapi.json)
|
|
300
|
+
- [MCP server](https://cleave.fi/docs/agents/mcp): `https://api.cleave.fi/mcp`
|
|
301
|
+
|
|
302
|
+
## Terms
|
|
303
|
+
|
|
304
|
+
Use of the API is subject to Cleave's [Terms of Use](https://cleave.fi/terms)
|
|
305
|
+
and [Privacy Policy](https://cleave.fi/privacy).
|
|
306
|
+
|
|
307
|
+
## License
|
|
308
|
+
|
|
309
|
+
MIT
|