@cleave-protocol/ai-sdk 0.0.0-stage → 0.1.1
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.
- package/LICENSE +21 -0
- package/README.md +339 -2
- package/dist/index.cjs +1300 -0
- package/dist/index.d.cts +83 -0
- package/dist/index.d.ts +83 -0
- package/dist/index.js +1263 -0
- package/package.json +86 -4
package/LICENSE
ADDED
|
@@ -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.
|
package/README.md
CHANGED
|
@@ -1,3 +1,340 @@
|
|
|
1
|
-
#
|
|
1
|
+
# @cleave-protocol/ai-sdk
|
|
2
2
|
|
|
3
|
-
|
|
3
|
+
Cleave swap tools for [Vercel AI SDK](https://ai-sdk.dev) agents.
|
|
4
|
+
|
|
5
|
+
Cleave is a DEX aggregator on Ethereum, Arbitrum, Base, BNB Chain, Polygon
|
|
6
|
+
and Optimism. It splits a trade across pools and paths, and swaps between
|
|
7
|
+
chains in one step. Quotes include Cleave's fees in the price.
|
|
8
|
+
|
|
9
|
+
These tools plan swaps and return unsigned calls. The user's own wallet
|
|
10
|
+
signs and sends them. They sign nothing; the only thing they send is a gas
|
|
11
|
+
order the wallet has signed.
|
|
12
|
+
|
|
13
|
+
## Install
|
|
14
|
+
|
|
15
|
+
```sh
|
|
16
|
+
npm install @cleave-protocol/ai-sdk ai zod
|
|
17
|
+
```
|
|
18
|
+
|
|
19
|
+
No API key is needed.
|
|
20
|
+
|
|
21
|
+
## Usage
|
|
22
|
+
|
|
23
|
+
```ts
|
|
24
|
+
import { generateText, isStepCount } from 'ai';
|
|
25
|
+
import { cleaveTools } from '@cleave-protocol/ai-sdk';
|
|
26
|
+
|
|
27
|
+
const { text } = await generateText({
|
|
28
|
+
model: 'openai/gpt-5-mini',
|
|
29
|
+
prompt:
|
|
30
|
+
'Plan a swap of 25 USDC to WETH on Base for wallet 0x00000000000000000000000000000000000A11cE.',
|
|
31
|
+
tools: cleaveTools(),
|
|
32
|
+
stopWhen: isStepCount(5),
|
|
33
|
+
});
|
|
34
|
+
|
|
35
|
+
console.log(text);
|
|
36
|
+
```
|
|
37
|
+
|
|
38
|
+
On `ai` 6, use `stepCountIs` in place of `isStepCount`.
|
|
39
|
+
|
|
40
|
+
`cleaveTools()` includes `getGas`. Its second call passes a gas order the
|
|
41
|
+
wallet has signed to Cleave's relayer. That call waits for your approval
|
|
42
|
+
by default; see [Approving a gas order](#approving-a-gas-order).
|
|
43
|
+
|
|
44
|
+
Each tool can also be added on its own:
|
|
45
|
+
|
|
46
|
+
```ts
|
|
47
|
+
import { findToken, planSwap } from '@cleave-protocol/ai-sdk';
|
|
48
|
+
|
|
49
|
+
const tools = { planSwap: planSwap(), findToken: findToken() };
|
|
50
|
+
```
|
|
51
|
+
|
|
52
|
+
## Tools
|
|
53
|
+
|
|
54
|
+
The tools match Cleave's MCP server tools and call the public
|
|
55
|
+
[Agent REST API](https://cleave.fi/docs/agents/rest).
|
|
56
|
+
|
|
57
|
+
| Tool | MCP tool | What it does |
|
|
58
|
+
| --- | --- | --- |
|
|
59
|
+
| `planSwap` | `cleave_plan_swap` | Plans a swap on one chain, a cross-chain swap or a bridge, and returns unsigned calls in signing order |
|
|
60
|
+
| `trackSwap` | `cleave_track_swap` | Reports the state of a sent plan or gas order, and what arrived |
|
|
61
|
+
| `findToken` | `cleave_find_token` | Finds a token by symbol or address and flags look-alikes |
|
|
62
|
+
| `getGas` | `cleave_get_gas` | Offers native gas for USDC, then passes the order the wallet signed to Cleave's relayer |
|
|
63
|
+
|
|
64
|
+
The usual flow:
|
|
65
|
+
|
|
66
|
+
1. `findToken`, when a symbol is ambiguous.
|
|
67
|
+
2. `planSwap`, with the wallet, the tokens and the amount.
|
|
68
|
+
3. The wallet signs the returned `calls` in order, before `validUntil`.
|
|
69
|
+
4. `trackSwap`, with the chain, the transaction hash and the plan's
|
|
70
|
+
`requestId`.
|
|
71
|
+
|
|
72
|
+
Check with `trackSwap` before sending a plan again: a plan still in flight
|
|
73
|
+
that is sent twice can pay twice.
|
|
74
|
+
|
|
75
|
+
A plan states its notices first, then the outcome kind, the costs charged
|
|
76
|
+
by others, the expected time and `validUntil`. A `block` notice means no
|
|
77
|
+
calls. A `warn` notice, or a recipient other than the wallet, sets
|
|
78
|
+
`requiresConfirmation`. The token to receive must be on Cleave's token
|
|
79
|
+
list.
|
|
80
|
+
|
|
81
|
+
Before any wallet signs, show the user the warnings, recipient, amounts
|
|
82
|
+
and slippage, and get their confirmation. Do not raise slippage above the
|
|
83
|
+
default 50 bps without the user's consent. Your signer enforces this: the
|
|
84
|
+
tools only plan.
|
|
85
|
+
|
|
86
|
+
`findToken` returns up to ten candidates for a symbol. When the user can
|
|
87
|
+
give a verified contract address, prefer it to a symbol.
|
|
88
|
+
|
|
89
|
+
Field-by-field answers are documented at
|
|
90
|
+
[cleave.fi/docs/agents/mcp](https://cleave.fi/docs/agents/mcp).
|
|
91
|
+
|
|
92
|
+
## Keys and signing
|
|
93
|
+
|
|
94
|
+
- The tools take no private keys.
|
|
95
|
+
- They sign nothing; the only thing they send is a gas order the wallet
|
|
96
|
+
has signed.
|
|
97
|
+
- `planSwap` returns unsigned calls as `{ chainId, from, to, data, value }`.
|
|
98
|
+
- `getGas` returns an offer with one USDC authorization to sign. Called
|
|
99
|
+
again with that offer, the wallet and the wallet's signature, it passes
|
|
100
|
+
the signed order to Cleave's relayer. That second call has
|
|
101
|
+
`needsApproval` set, so the AI SDK asks for your approval before it runs.
|
|
102
|
+
|
|
103
|
+
## Address checks
|
|
104
|
+
|
|
105
|
+
Before a `planSwap` plan or a `getGas` offer reaches the model, the package
|
|
106
|
+
checks it against per-chain contract addresses, rails and tokens pinned in
|
|
107
|
+
this package:
|
|
108
|
+
|
|
109
|
+
- `planSwap`: every call is on the planned chain and from the wallet. The
|
|
110
|
+
last call goes to a listed contract and function that fits the route,
|
|
111
|
+
sends to the planned chain and pays the requested recipient where its
|
|
112
|
+
calldata names one; refunds go to the wallet. Any call before it is an
|
|
113
|
+
approve of the sell token to that contract for exactly what the last
|
|
114
|
+
call pulls. For USDC and the chain's native or wrapped native coin, the
|
|
115
|
+
amount sold is the requested amount. The native value is what the
|
|
116
|
+
native amount sold and the Stargate native messaging fee need.
|
|
117
|
+
- `getGas`: the authorization is a USDC `ReceiveWithAuthorization` from
|
|
118
|
+
the wallet on the paying chain. The order carries the requested amount,
|
|
119
|
+
recipient and refund address, the pinned contracts, rail and fee
|
|
120
|
+
recipient, and the fee and amounts the offer shows. The order hash and
|
|
121
|
+
the payment address are recomputed and must match what is signed.
|
|
122
|
+
|
|
123
|
+
A plan or offer that fails a check is not passed on; the tool returns only
|
|
124
|
+
this:
|
|
125
|
+
|
|
126
|
+
```json
|
|
127
|
+
{
|
|
128
|
+
"error": {
|
|
129
|
+
"code": "address_not_allowed",
|
|
130
|
+
"message": "P2: the main call's target is not on the allowlist",
|
|
131
|
+
"retryable": false
|
|
132
|
+
}
|
|
133
|
+
}
|
|
134
|
+
```
|
|
135
|
+
|
|
136
|
+
A plan or offer that passes is returned with only its documented
|
|
137
|
+
top-level fields.
|
|
138
|
+
|
|
139
|
+
What these checks do not cover:
|
|
140
|
+
|
|
141
|
+
- The price, the route and the amount received.
|
|
142
|
+
- On routes that swap on arrival or pay a fee through the bridge message,
|
|
143
|
+
the funds go to the Across handler or Cleave's composer, and that
|
|
144
|
+
message decides who receives them. These checks do not read it, so a
|
|
145
|
+
compromised API could redirect those funds. Have a person check such a
|
|
146
|
+
plan before signing.
|
|
147
|
+
- When `sellToken` is a symbol other than USDC or the chain's native or
|
|
148
|
+
wrapped native coin, the API resolves it to an address. Check
|
|
149
|
+
`send.token` before signing.
|
|
150
|
+
- For any other sell token, given by symbol or by address, the package
|
|
151
|
+
does not know its decimals, so the amount in the approve and the
|
|
152
|
+
calldata is not checked against the request. Check the approve amount
|
|
153
|
+
and `pullsAtMost` before signing.
|
|
154
|
+
- The Stargate native messaging fee is the API's figure. The checks only
|
|
155
|
+
match it with the call's value; nothing bounds it.
|
|
156
|
+
- `next` and the notices are text from the API.
|
|
157
|
+
|
|
158
|
+
After Cleave redeploys a contract, plans that use it fail these checks
|
|
159
|
+
until a package update pins the new address.
|
|
160
|
+
|
|
161
|
+
Agents that call the API directly should pass each answer through
|
|
162
|
+
`screenPlanSwap(request, body)` or `screenGetGas(request, body)` and use
|
|
163
|
+
only what they return: the refusal, or the answer cut to its documented
|
|
164
|
+
fields. `checkPlanSwap` and `checkGetGas` return only a verdict; a pass
|
|
165
|
+
from them does not make the original answer safe to forward, since it
|
|
166
|
+
can carry fields the checks do not read.
|
|
167
|
+
|
|
168
|
+
Do not pair these tools with a wallet that signs without a human looking.
|
|
169
|
+
|
|
170
|
+
## Gas top-ups
|
|
171
|
+
|
|
172
|
+
`getGas` takes two calls.
|
|
173
|
+
|
|
174
|
+
1. The offer: `payChainId`, `chainId`, `recipient` and `amount`.
|
|
175
|
+
- `typedData`, the USDC authorization to sign, comes back only when
|
|
176
|
+
`wallet` is passed.
|
|
177
|
+
- An order from another chain needs `wallet` or `refundTo`. On one
|
|
178
|
+
chain, a refund goes to `refundTo`, else `wallet`, else the recipient.
|
|
179
|
+
- `amount` is USDC with at most 6 decimals, from 2 to 50 USDC; gas
|
|
180
|
+
delivered on Ethereum needs at least 5 USDC.
|
|
181
|
+
2. The order: `offer`, `wallet` and `signature`. The tool passes the
|
|
182
|
+
signed order to Cleave's relayer.
|
|
183
|
+
|
|
184
|
+
An authorization that is never used never debits the wallet. Once an
|
|
185
|
+
order is funded, follow `trackSwap` with its `requestId`
|
|
186
|
+
(`g:<payChainId>:<orderHash>`) and report the status it gives; do not
|
|
187
|
+
assume a refund.
|
|
188
|
+
|
|
189
|
+
### Approving a gas order
|
|
190
|
+
|
|
191
|
+
The second `getGas` call has `needsApproval` set, so `generateText` stops
|
|
192
|
+
with a `tool-approval-request` instead of running it. Your app shows the
|
|
193
|
+
request to the user and resumes with their answer:
|
|
194
|
+
|
|
195
|
+
```ts
|
|
196
|
+
import { generateText, isStepCount } from 'ai';
|
|
197
|
+
import type { ModelMessage, ToolApprovalResponse } from 'ai';
|
|
198
|
+
import { cleaveTools } from '@cleave-protocol/ai-sdk';
|
|
199
|
+
|
|
200
|
+
const tools = cleaveTools();
|
|
201
|
+
const messages: ModelMessage[] = [
|
|
202
|
+
{ role: 'user', content: 'Submit the gas order my wallet just signed.' },
|
|
203
|
+
];
|
|
204
|
+
|
|
205
|
+
const first = await generateText({
|
|
206
|
+
model: 'openai/gpt-5-mini',
|
|
207
|
+
tools,
|
|
208
|
+
messages,
|
|
209
|
+
stopWhen: isStepCount(5),
|
|
210
|
+
});
|
|
211
|
+
messages.push(...first.response.messages);
|
|
212
|
+
|
|
213
|
+
const approvals: ToolApprovalResponse[] = [];
|
|
214
|
+
for (const part of first.content) {
|
|
215
|
+
if (part.type === 'tool-approval-request') {
|
|
216
|
+
approvals.push({
|
|
217
|
+
type: 'tool-approval-response',
|
|
218
|
+
approvalId: part.approvalId,
|
|
219
|
+
approved: await confirmWithUser(part.toolCall.input),
|
|
220
|
+
});
|
|
221
|
+
}
|
|
222
|
+
}
|
|
223
|
+
|
|
224
|
+
if (approvals.length > 0) {
|
|
225
|
+
messages.push({ role: 'tool', content: approvals });
|
|
226
|
+
const second = await generateText({
|
|
227
|
+
model: 'openai/gpt-5-mini',
|
|
228
|
+
tools,
|
|
229
|
+
messages,
|
|
230
|
+
stopWhen: isStepCount(5),
|
|
231
|
+
});
|
|
232
|
+
console.log(second.text);
|
|
233
|
+
}
|
|
234
|
+
```
|
|
235
|
+
|
|
236
|
+
`confirmWithUser` stands for your own prompt to the user.
|
|
237
|
+
|
|
238
|
+
- This approval comes after the wallet has signed. Before the wallet
|
|
239
|
+
signs, show the user the order from the offer: the payer, the
|
|
240
|
+
recipient, the refund address, the amount and the fee.
|
|
241
|
+
- Calling a tool's `.execute()` directly skips the AI SDK's approval step.
|
|
242
|
+
- `getGasApproval: false` turns the approval off.
|
|
243
|
+
|
|
244
|
+
## Options
|
|
245
|
+
|
|
246
|
+
Every factory, and `cleaveTools`, takes the same optional settings:
|
|
247
|
+
|
|
248
|
+
```ts
|
|
249
|
+
cleaveTools({
|
|
250
|
+
baseUrl: 'https://api.cleave.fi',
|
|
251
|
+
apiKey: process.env.CLEAVE_API_KEY,
|
|
252
|
+
timeoutMs: 30000,
|
|
253
|
+
fetch: globalThis.fetch,
|
|
254
|
+
getGasApproval: true,
|
|
255
|
+
});
|
|
256
|
+
```
|
|
257
|
+
|
|
258
|
+
| Option | Default | Meaning |
|
|
259
|
+
| --- | --- | --- |
|
|
260
|
+
| `baseUrl` | `https://api.cleave.fi` | API origin |
|
|
261
|
+
| `apiKey` | none | Optional partner key, sent to `baseUrl` as `Authorization: Bearer`. An unknown or revoked key fails every call with `unauthorized`; there is no keyless fallback |
|
|
262
|
+
| `timeoutMs` | `30000` | Time limit for each call, combined with the AI SDK's abort signal |
|
|
263
|
+
| `fetch` | `globalThis.fetch` | A custom fetch |
|
|
264
|
+
| `getGasApproval` | `true` | Whether `getGas` asks for approval before passing a signed order |
|
|
265
|
+
|
|
266
|
+
## Errors
|
|
267
|
+
|
|
268
|
+
From `planSwap` and `getGas`, a refusal from the API comes back as the
|
|
269
|
+
tool's result with only its `apiVersion`, `requestId` and `error` fields,
|
|
270
|
+
so the model can read it. `trackSwap` and `findToken` return it unchanged:
|
|
271
|
+
|
|
272
|
+
```json
|
|
273
|
+
{
|
|
274
|
+
"apiVersion": "1",
|
|
275
|
+
"requestId": "...",
|
|
276
|
+
"error": {
|
|
277
|
+
"code": "not_found",
|
|
278
|
+
"reason": "unknown_order",
|
|
279
|
+
"message": "...",
|
|
280
|
+
"detail": "...",
|
|
281
|
+
"retryable": false
|
|
282
|
+
}
|
|
283
|
+
}
|
|
284
|
+
```
|
|
285
|
+
|
|
286
|
+
`retryable: true` means the same call can succeed later; `retryAfterSec`,
|
|
287
|
+
when present, says when.
|
|
288
|
+
|
|
289
|
+
`CleaveApiError` is thrown when there is no API answer to return:
|
|
290
|
+
|
|
291
|
+
- a network error, a timeout or an abort: `status` is `0` and `cause`
|
|
292
|
+
holds the original error;
|
|
293
|
+
- a successful answer that is not a JSON object: `status` is the HTTP
|
|
294
|
+
status.
|
|
295
|
+
|
|
296
|
+
An error status whose body has no `error` object comes back as the tool's
|
|
297
|
+
result `{ "error": { "code": "http_error", "message": "HTTP 502", "retryable": true } }`,
|
|
298
|
+
with nothing else from the body; `retryable` is true for 5xx and 429.
|
|
299
|
+
|
|
300
|
+
`requestId` carries the `x-request-id` header when the API sent one.
|
|
301
|
+
|
|
302
|
+
Aborting a call, or a timeout, does not cancel a gas order the API has
|
|
303
|
+
already accepted. If a `getGas` submission fails this way, track
|
|
304
|
+
`g:<payChainId>:<orderHash>` from the first offer before signing a new
|
|
305
|
+
one.
|
|
306
|
+
|
|
307
|
+
## Server side only
|
|
308
|
+
|
|
309
|
+
Run these tools on a server, in a bot or in agent code. The Agent REST API
|
|
310
|
+
refuses requests that carry an `Origin` header, as browsers send, with
|
|
311
|
+
`403` and code `forbidden_origin`.
|
|
312
|
+
|
|
313
|
+
## Limits
|
|
314
|
+
|
|
315
|
+
- Without a key, each client IP can make 40 requests per 10 seconds and
|
|
316
|
+
120 per minute.
|
|
317
|
+
- From a recognised hosted platform, where many agents share one address,
|
|
318
|
+
a request with a valid `wallet` in its body is counted by that wallet
|
|
319
|
+
instead. `findToken` and `trackSwap` take no `wallet`, so they stay
|
|
320
|
+
counted by IP.
|
|
321
|
+
- With `apiKey`, calls count against the partner key's own limits.
|
|
322
|
+
- All keyless agents share one planning capacity, used by `planSwap` and
|
|
323
|
+
by `getGas` offers; a cross-chain plan or offer counts twice. When it is
|
|
324
|
+
busy, the answer is `rate_limited` with reason `agent_limit` and a short
|
|
325
|
+
`retryAfterSec`.
|
|
326
|
+
|
|
327
|
+
## Links
|
|
328
|
+
|
|
329
|
+
- [Cleave for agents](https://cleave.fi/docs/agents)
|
|
330
|
+
- [Agent REST API](https://cleave.fi/docs/agents/rest)
|
|
331
|
+
- [Gas top-up](https://cleave.fi/docs/agents/gas)
|
|
332
|
+
- [MCP server](https://cleave.fi/docs/agents/mcp): `https://api.cleave.fi/mcp`
|
|
333
|
+
|
|
334
|
+
Use of the API is subject to Cleave's
|
|
335
|
+
[Terms of Use](https://cleave.fi/terms) and
|
|
336
|
+
[Privacy Policy](https://cleave.fi/privacy).
|
|
337
|
+
|
|
338
|
+
## License
|
|
339
|
+
|
|
340
|
+
MIT
|