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.
@@ -0,0 +1,10 @@
1
+ __pycache__/
2
+ *.py[cod]
3
+ .venv/
4
+ dist/
5
+ build/
6
+ *.egg-info/
7
+ .pytest_cache/
8
+ .mypy_cache/
9
+ .ruff_cache/
10
+ .coverage
@@ -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