@orkid-labs/sdk 0.1.0
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/README.md +689 -0
- package/dist/chains-QHQI25WO.mjs +20 -0
- package/dist/chunk-3UAOMCO3.mjs +227 -0
- package/dist/chunk-7T23KZR4.mjs +122 -0
- package/dist/chunk-PMDWMIGW.mjs +164 -0
- package/dist/cli.d.mts +1 -0
- package/dist/cli.d.ts +1 -0
- package/dist/cli.js +856 -0
- package/dist/cli.mjs +324 -0
- package/dist/index.d.mts +542 -0
- package/dist/index.d.ts +542 -0
- package/dist/index.js +640 -0
- package/dist/index.mjs +140 -0
- package/dist/viem-CZMIEVIX.mjs +23 -0
- package/openapi.yaml +522 -0
- package/package.json +66 -0
package/README.md
ADDED
|
@@ -0,0 +1,689 @@
|
|
|
1
|
+
# Orkid Swap API — Integration Kit
|
|
2
|
+
|
|
3
|
+
This kit contains everything an external integrator needs to swap through the Orkid solver using the two public API endpoints:
|
|
4
|
+
|
|
5
|
+
- `POST /api/v1/route` — get an executable quote
|
|
6
|
+
- `POST /api/v1/solve` — sign a Permit2 message and execute gaslessly
|
|
7
|
+
|
|
8
|
+
It includes a TypeScript SDK, request/response types, signing helpers for **viem** and **ethers**, copy-paste examples, an OpenAPI spec, and a step-by-step integration guide.
|
|
9
|
+
|
|
10
|
+
---
|
|
11
|
+
|
|
12
|
+
## Table of Contents
|
|
13
|
+
|
|
14
|
+
1. [What Orkid gives an integrator](#what-orkid-gives-an-integrator)
|
|
15
|
+
2. [Supported chains](#supported-chains)
|
|
16
|
+
3. [Environments: production vs sandbox](#environments-production-vs-sandbox)
|
|
17
|
+
4. [API key & rate limits](#api-key--rate-limits)
|
|
18
|
+
5. [High-level flow](#high-level-flow)
|
|
19
|
+
6. [Endpoint 1: /api/v1/route](#endpoint-1-apiv1route)
|
|
20
|
+
7. [Endpoint 2: /api/v1/solve](#endpoint-2-apiv1solve)
|
|
21
|
+
8. [Permit2 signing walkthrough](#permit2-signing-walkthrough)
|
|
22
|
+
9. [Error & retry guide](#error--retry-guide)
|
|
23
|
+
10. [SDK reference](#sdk-reference)
|
|
24
|
+
11. [CLI](#cli)
|
|
25
|
+
12. [Reporting endpoints](#reporting-endpoints)
|
|
26
|
+
13. [Examples](#examples)
|
|
27
|
+
14. [OpenAPI](#openapi)
|
|
28
|
+
|
|
29
|
+
---
|
|
30
|
+
|
|
31
|
+
## What Orkid gives an integrator
|
|
32
|
+
|
|
33
|
+
- **Same-chain, gasless swaps** on Base, Ethereum, Arbitrum, and Polygon.
|
|
34
|
+
- **Competitive Orkid fee (contact us for pricing)** taken from the input token.
|
|
35
|
+
- **Bundled / gasless execution** — the Orkid solver submits the transaction and pays the gas.
|
|
36
|
+
- **MEV-aware routing** using Tycho's live pool graph and simulation. On Ethereum, transactions are broadcast through Flashbots Protect (private mempool) to protect against sandwich attacks.
|
|
37
|
+
- **Per-account volume & rebate tracking** in the Orkid board admin.
|
|
38
|
+
- **Non-custodial** — the solver uses Permit2 to pull the input token and the net output goes straight to the user.
|
|
39
|
+
- **Sandbox environment** — a dedicated dry-run endpoint you can hammer without touching real flows.
|
|
40
|
+
|
|
41
|
+
---
|
|
42
|
+
|
|
43
|
+
## Supported chains
|
|
44
|
+
|
|
45
|
+
| Chain | ID | Status | TVMExecutor (spender) | Min notional |
|
|
46
|
+
| --- | --- | --- | --- | --- |
|
|
47
|
+
| Base | 8453 | **Live** | `0x60AB9E090abF9B6BcFbA16017eE18AEf5f2c2289` | $20 |
|
|
48
|
+
| Ethereum | 1 | **Live** | `0xCfc33b521190FcD414c8f81f479749c4dCE8f69b` | $200 |
|
|
49
|
+
| Arbitrum | 42161 | **Live** | `0xf57235609bf99fb0b017e95b3bc33a606a541374` | $50 |
|
|
50
|
+
| Polygon | 137 | **Live** | `0xd633Ea84E6E2Db002C14C6fe3A064eE2cc4E258c` | $50 |
|
|
51
|
+
|
|
52
|
+
All four chains use the canonical Permit2 contract: `0x000000000022D473030F116dDEE9F6B43aC78BA3`.
|
|
53
|
+
|
|
54
|
+
The `spender` field in your signed Permit2 message must match the TVMExecutor address for the target chain. The SDK handles this automatically — see [Permit2 signing walkthrough](#permit2-signing-walkthrough) for manual integrations.
|
|
55
|
+
|
|
56
|
+
---
|
|
57
|
+
|
|
58
|
+
## Environments: production vs sandbox
|
|
59
|
+
|
|
60
|
+
Orkid provides two environments. Both use the same API paths (`/api/v1/route`, `/api/v1/solve`, etc.) — the **base URL** determines which environment you hit.
|
|
61
|
+
|
|
62
|
+
| Environment | Base URL | Solve behavior | Volume tracked |
|
|
63
|
+
| --- | --- | --- | --- |
|
|
64
|
+
| **Production** | `https://orkidlabs.xyz` | Real execution (submits on-chain tx) | Yes — counts toward rebates |
|
|
65
|
+
| **Sandbox** | `https://sandbox.orkidlabs.com` | **Dry-run only** (never submits) | No — sandbox traffic is isolated |
|
|
66
|
+
|
|
67
|
+
### Sandbox
|
|
68
|
+
|
|
69
|
+
The sandbox is a dedicated environment for integration testing. It:
|
|
70
|
+
|
|
71
|
+
- Forwards `/api/v1/route` to the **real solvers** — you get authentic quotes, routing, and latency.
|
|
72
|
+
- Forces `/api/v1/solve` to `dryRun: true` **regardless of what you send** — the solver encodes and validates the transaction but never submits it.
|
|
73
|
+
- Uses **separate API keys** from production.
|
|
74
|
+
- Does **not** record usage or rebate volume.
|
|
75
|
+
- Has its own rate limit (300 req/min for the pilot key).
|
|
76
|
+
|
|
77
|
+
This means you can hammer the sandbox with thousands of test requests, try different token pairs and amounts, validate your Permit2 signing flow end-to-end, and never risk a real on-chain transaction or pollute your production volume accounting.
|
|
78
|
+
|
|
79
|
+
### Getting a sandbox key
|
|
80
|
+
|
|
81
|
+
Contact Orkid for a sandbox API key. Sandbox keys are 40-character hex strings, separate from production keys:
|
|
82
|
+
|
|
83
|
+
```text
|
|
84
|
+
X-ORKID-API-Key: <sandbox-key>
|
|
85
|
+
```
|
|
86
|
+
|
|
87
|
+
### Using the sandbox with the SDK
|
|
88
|
+
|
|
89
|
+
```typescript
|
|
90
|
+
import { OrkidClient, SANDBOX_BASE_URL } from '@orkid/sdk'
|
|
91
|
+
|
|
92
|
+
const sandbox = new OrkidClient({
|
|
93
|
+
apiKey: process.env.ORKID_SANDBOX_KEY!,
|
|
94
|
+
baseUrl: SANDBOX_BASE_URL, // https://sandbox.orkidlabs.com
|
|
95
|
+
})
|
|
96
|
+
|
|
97
|
+
// Real quote from the real solver
|
|
98
|
+
const quote = await sandbox.getQuote({
|
|
99
|
+
from: 'USDC',
|
|
100
|
+
to: 'WETH',
|
|
101
|
+
amount: '25.0',
|
|
102
|
+
chain: 'base',
|
|
103
|
+
})
|
|
104
|
+
|
|
105
|
+
// Dry-run solve — encodes and validates but never submits
|
|
106
|
+
const result = await sandbox.solve({
|
|
107
|
+
from: 'USDC',
|
|
108
|
+
to: 'WETH',
|
|
109
|
+
amount: '25.0',
|
|
110
|
+
chain: 'base',
|
|
111
|
+
user: '0x...',
|
|
112
|
+
fromAddress: '0x833589fcd6edb6e08f4c7c32d4f71b54bda02913',
|
|
113
|
+
fromDecimals: 6,
|
|
114
|
+
toAddress: '0x4200000000000000000000000000000000000006',
|
|
115
|
+
toDecimals: 18,
|
|
116
|
+
dryRun: false, // ignored — sandbox always forces dryRun: true
|
|
117
|
+
permit: signed.permit,
|
|
118
|
+
signature: signed.signature,
|
|
119
|
+
})
|
|
120
|
+
// result.transaction.to / .data are real calldata
|
|
121
|
+
// result.transaction.txHash is absent (never submitted)
|
|
122
|
+
```
|
|
123
|
+
|
|
124
|
+
### Using the sandbox with curl
|
|
125
|
+
|
|
126
|
+
```bash
|
|
127
|
+
# Health check (no auth required)
|
|
128
|
+
curl https://sandbox.orkidlabs.com/health
|
|
129
|
+
|
|
130
|
+
# Route (real quote)
|
|
131
|
+
curl -X POST https://sandbox.orkidlabs.com/api/v1/route \
|
|
132
|
+
-H "X-ORKID-API-Key: <sandbox-key>" \
|
|
133
|
+
-H "Content-Type: application/json" \
|
|
134
|
+
-d '{"from":"USDC","to":"WETH","amount":"25.0","chain":"base","fromAddress":"0x833589fcd6edb6e08f4c7c32d4f71b54bda02913","fromDecimals":6,"toAddress":"0x4200000000000000000000000000000000000006","toDecimals":18}'
|
|
135
|
+
|
|
136
|
+
# Solve (always dry-run — never submits)
|
|
137
|
+
curl -X POST https://sandbox.orkidlabs.com/api/v1/solve \
|
|
138
|
+
-H "X-ORKID-API-Key: <sandbox-key>" \
|
|
139
|
+
-H "Content-Type: application/json" \
|
|
140
|
+
-d '{"from":"USDC","to":"WETH","amount":"25.0","chain":"base","user":"0x...","fromAddress":"0x...","fromDecimals":6,"toAddress":"0x...","toDecimals":18,"dryRun":false,"permit":{...},"signature":"0x..."}'
|
|
141
|
+
```
|
|
142
|
+
|
|
143
|
+
### Sandbox safety guarantees
|
|
144
|
+
|
|
145
|
+
- **No on-chain execution**: the sandbox proxy intercepts every `/api/v1/solve` and forces `dryRun: true` before forwarding to the solver. Even if you send `dryRun: false`, the sandbox ignores it.
|
|
146
|
+
- **No production volume**: sandbox requests do not call the production usage-tracking or rebate-recording endpoints.
|
|
147
|
+
- **Separate keys**: sandbox API keys are distinct from production keys. A production key will not work on the sandbox and vice versa.
|
|
148
|
+
- **Real routing**: `/api/v1/route` hits the actual per-chain solvers, so quotes reflect live pool state and real latency.
|
|
149
|
+
|
|
150
|
+
---
|
|
151
|
+
|
|
152
|
+
## API key & rate limits
|
|
153
|
+
|
|
154
|
+
Every programmatic request must include:
|
|
155
|
+
|
|
156
|
+
```text
|
|
157
|
+
X-ORKID-API-Key: <40-char-hex-key>
|
|
158
|
+
```
|
|
159
|
+
|
|
160
|
+
API keys are 40-character hex strings. Only a SHA-256 hash is stored on Orkid's side, so the plaintext key is shown once when it is created.
|
|
161
|
+
|
|
162
|
+
To create or rotate a key, log in to the Orkid board admin (`/board/admin`) with a master account, open the API Accounts section, and generate a key for the partner account.
|
|
163
|
+
|
|
164
|
+
Default rate limits:
|
|
165
|
+
|
|
166
|
+
| Environment | Tier | Requests / minute |
|
|
167
|
+
| --- | --- | --- |
|
|
168
|
+
| Production | free | 60 |
|
|
169
|
+
| Production | pro | 120 |
|
|
170
|
+
| Production | enterprise | 1000 |
|
|
171
|
+
| Sandbox | pilot | 300 |
|
|
172
|
+
|
|
173
|
+
---
|
|
174
|
+
|
|
175
|
+
## High-level flow
|
|
176
|
+
|
|
177
|
+
1. **Get a quote** (`/api/v1/route`)
|
|
178
|
+
2. **Approve the input token to Permit2** (one-time per token)
|
|
179
|
+
3. **Find an unused Permit2 nonce** by reading the nonce bitmap on-chain
|
|
180
|
+
4. **Sign a Permit2 `PermitTransferFrom` message** with the TVMExecutor as `spender`
|
|
181
|
+
5. **Call `/api/v1/solve`** with `dryRun: false` to submit gaslessly
|
|
182
|
+
6. Optionally fall back to `dryRun: true` and have the user send the transaction themselves if the solver cannot subsidize gas
|
|
183
|
+
|
|
184
|
+
> **Sandbox tip:** Steps 1-5 work identically in the sandbox. Step 5 always behaves as `dryRun: true` — you get back encoded calldata but no transaction is submitted. This is the perfect way to validate your signing flow before going live.
|
|
185
|
+
|
|
186
|
+
---
|
|
187
|
+
|
|
188
|
+
## Endpoint 1: `/api/v1/route`
|
|
189
|
+
|
|
190
|
+
### Request
|
|
191
|
+
|
|
192
|
+
```json
|
|
193
|
+
POST https://orkidlabs.xyz/api/v1/route
|
|
194
|
+
Content-Type: application/json
|
|
195
|
+
X-ORKID-API-Key: <key>
|
|
196
|
+
|
|
197
|
+
{
|
|
198
|
+
"from": "USDC",
|
|
199
|
+
"to": "WETH",
|
|
200
|
+
"amount": "25.0",
|
|
201
|
+
"chain": "base",
|
|
202
|
+
"fromAddress": "0x833589fcd6edb6e08f4c7c32d4f71b54bda02913",
|
|
203
|
+
"fromDecimals": 6,
|
|
204
|
+
"toAddress": "0x4200000000000000000000000000000000000006",
|
|
205
|
+
"toDecimals": 18
|
|
206
|
+
}
|
|
207
|
+
```
|
|
208
|
+
|
|
209
|
+
`from` and `to` can be token symbols (e.g. `USDC`, `WETH`). Explicit `*_address` + `*_decimals` is recommended because symbol resolution is server-side and may not cover every token.
|
|
210
|
+
|
|
211
|
+
`chain` accepts: `base`, `ethereum`, `arbitrum`, `polygon`.
|
|
212
|
+
|
|
213
|
+
### Response
|
|
214
|
+
|
|
215
|
+
```json
|
|
216
|
+
{
|
|
217
|
+
"ok": true,
|
|
218
|
+
"quote": {
|
|
219
|
+
"amountIn": "25.000000",
|
|
220
|
+
"amountOut": "0.010184",
|
|
221
|
+
"amountOutRaw": "10183963710975259",
|
|
222
|
+
"volumeUsd": 25,
|
|
223
|
+
"rate": "0.000407 WETH/USDC",
|
|
224
|
+
"protocol": "aerodrome_slipstreams",
|
|
225
|
+
"poolAddress": "0xc758d81b9b81a6fcdad075bd471874a2c46b54e0",
|
|
226
|
+
"priceImpactBps": 9.66
|
|
227
|
+
},
|
|
228
|
+
"gaslessEligible": true,
|
|
229
|
+
"computeMs": 142
|
|
230
|
+
}
|
|
231
|
+
```
|
|
232
|
+
|
|
233
|
+
### Error response
|
|
234
|
+
|
|
235
|
+
```json
|
|
236
|
+
{
|
|
237
|
+
"ok": false,
|
|
238
|
+
"error": "Swap too small for gasless execution: $5.00 below $20 minimum on base.",
|
|
239
|
+
"gaslessEligible": false,
|
|
240
|
+
"computeMs": 45
|
|
241
|
+
}
|
|
242
|
+
```
|
|
243
|
+
|
|
244
|
+
---
|
|
245
|
+
|
|
246
|
+
## Endpoint 2: `/api/v1/solve`
|
|
247
|
+
|
|
248
|
+
### Request Shape
|
|
249
|
+
|
|
250
|
+
```json
|
|
251
|
+
POST https://orkidlabs.xyz/api/v1/solve
|
|
252
|
+
Content-Type: application/json
|
|
253
|
+
X-ORKID-API-Key: <key>
|
|
254
|
+
|
|
255
|
+
{
|
|
256
|
+
"from": "USDC",
|
|
257
|
+
"to": "WETH",
|
|
258
|
+
"amount": "25.0",
|
|
259
|
+
"chain": "base",
|
|
260
|
+
"user": "0x...",
|
|
261
|
+
"fromAddress": "0x833589fcd6edb6e08f4c7c32d4f71b54bda02913",
|
|
262
|
+
"fromDecimals": 6,
|
|
263
|
+
"toAddress": "0x4200000000000000000000000000000000000006",
|
|
264
|
+
"toDecimals": 18,
|
|
265
|
+
"dryRun": false,
|
|
266
|
+
"slippageBps": 5,
|
|
267
|
+
"permit": {
|
|
268
|
+
"permitted": {
|
|
269
|
+
"token": "0x833589fcd6edb6e08f4c7c32d4f71b54bda02913",
|
|
270
|
+
"amount": "25000000"
|
|
271
|
+
},
|
|
272
|
+
"nonce": "123...",
|
|
273
|
+
"deadline": "1725561600"
|
|
274
|
+
},
|
|
275
|
+
"signature": "0x..."
|
|
276
|
+
}
|
|
277
|
+
```
|
|
278
|
+
|
|
279
|
+
### Success response
|
|
280
|
+
|
|
281
|
+
```json
|
|
282
|
+
{
|
|
283
|
+
"ok": true,
|
|
284
|
+
"quote": {
|
|
285
|
+
"amountIn": "25.000000",
|
|
286
|
+
"amountOut": "0.010184",
|
|
287
|
+
"amountOutRaw": "10183963710975259",
|
|
288
|
+
"volumeUsd": 25,
|
|
289
|
+
"rate": "0.000407 WETH/USDC",
|
|
290
|
+
"protocol": "aerodrome_slipstreams",
|
|
291
|
+
"poolAddress": "0xc758d81b9b81a6fcdad075bd471874a2c46b54e0",
|
|
292
|
+
"priceImpactBps": 9.66
|
|
293
|
+
},
|
|
294
|
+
"transaction": {
|
|
295
|
+
"txHash": "0x...",
|
|
296
|
+
"to": "0x60AB9E090abF9B6BcFbA16017eE18AEf5f2c2289",
|
|
297
|
+
"data": "0x...",
|
|
298
|
+
"value": "0",
|
|
299
|
+
"chain": "base"
|
|
300
|
+
},
|
|
301
|
+
"computeMs": 142
|
|
302
|
+
}
|
|
303
|
+
```
|
|
304
|
+
|
|
305
|
+
### `dryRun: true` response
|
|
306
|
+
|
|
307
|
+
When `dryRun` is `true`, the solver does **not** submit the transaction. It returns the same `transaction` object minus `txHash`. The integrator can then ask the user to send the transaction themselves:
|
|
308
|
+
|
|
309
|
+
```json
|
|
310
|
+
{
|
|
311
|
+
"ok": true,
|
|
312
|
+
"transaction": {
|
|
313
|
+
"to": "0x60AB9E090abF9B6BcFbA16017eE18AEf5f2c2289",
|
|
314
|
+
"data": "0x...",
|
|
315
|
+
"value": "0",
|
|
316
|
+
"chain": "base"
|
|
317
|
+
}
|
|
318
|
+
}
|
|
319
|
+
```
|
|
320
|
+
|
|
321
|
+
> **In the sandbox**, `/api/v1/solve` always returns this dry-run shape regardless of the `dryRun` value you send.
|
|
322
|
+
|
|
323
|
+
---
|
|
324
|
+
|
|
325
|
+
## Permit2 signing walkthrough
|
|
326
|
+
|
|
327
|
+
Permit2 lets a user approve a token transfer with an off-chain signature. The `PermitTransferFrom` signature authorizes a specific `spender` (the TVMExecutor) to pull an exact amount of a specific token before a deadline, using a specific one-time nonce.
|
|
328
|
+
|
|
329
|
+
### Step 1 — Approve the token to Permit2 (one-time)
|
|
330
|
+
|
|
331
|
+
Before the first swap of each token, the user must call:
|
|
332
|
+
|
|
333
|
+
```solidity
|
|
334
|
+
IERC20(token).approve(PERMIT2, type(uint256).max)
|
|
335
|
+
```
|
|
336
|
+
|
|
337
|
+
### Step 2 — Get the correct TVMExecutor address
|
|
338
|
+
|
|
339
|
+
The `spender` in the signed permit must match the TVMExecutor on the target chain:
|
|
340
|
+
|
|
341
|
+
| Chain | TVMExecutor |
|
|
342
|
+
| --- | --- |
|
|
343
|
+
| Base | `0x60AB9E090abF9B6BcFbA16017eE18AEf5f2c2289` |
|
|
344
|
+
| Ethereum | `0xCfc33b521190FcD414c8f81f479749c4dCE8f69b` |
|
|
345
|
+
| Arbitrum | `0xf57235609bf99fb0b017e95b3bc33a606a541374` |
|
|
346
|
+
| Polygon | `0xd633Ea84E6E2Db002C14C6fe3A064eE2cc4E258c` |
|
|
347
|
+
|
|
348
|
+
### Step 3 — Find an unused nonce
|
|
349
|
+
|
|
350
|
+
Permit2 stores a nonce bitmap for each user. If you reuse a nonce, the transaction reverts.
|
|
351
|
+
|
|
352
|
+
The nonce layout is:
|
|
353
|
+
|
|
354
|
+
```text
|
|
355
|
+
wordPos = uint248(nonce >> 8)
|
|
356
|
+
bitPos = uint8(nonce)
|
|
357
|
+
```
|
|
358
|
+
|
|
359
|
+
Read `nonceBitmap(owner, wordPos)` from the Permit2 contract until you find a word with an unset bit, then construct `(wordPos << 8) | bitPos`.
|
|
360
|
+
|
|
361
|
+
### Step 4 — Sign the Permit2 typed data
|
|
362
|
+
|
|
363
|
+
Domain:
|
|
364
|
+
|
|
365
|
+
```json
|
|
366
|
+
{
|
|
367
|
+
"name": "Permit2",
|
|
368
|
+
"chainId": 8453,
|
|
369
|
+
"verifyingContract": "0x000000000022D473030F116dDEE9F6B43aC78BA3"
|
|
370
|
+
}
|
|
371
|
+
```
|
|
372
|
+
|
|
373
|
+
Types:
|
|
374
|
+
|
|
375
|
+
```json
|
|
376
|
+
{
|
|
377
|
+
"PermitTransferFrom": [
|
|
378
|
+
{ "name": "permitted", "type": "TokenPermissions" },
|
|
379
|
+
{ "name": "spender", "type": "address" },
|
|
380
|
+
{ "name": "nonce", "type": "uint256" },
|
|
381
|
+
{ "name": "deadline", "type": "uint256" }
|
|
382
|
+
],
|
|
383
|
+
"TokenPermissions": [
|
|
384
|
+
{ "name": "token", "type": "address" },
|
|
385
|
+
{ "name": "amount", "type": "uint256" }
|
|
386
|
+
]
|
|
387
|
+
}
|
|
388
|
+
```
|
|
389
|
+
|
|
390
|
+
Message:
|
|
391
|
+
|
|
392
|
+
```json
|
|
393
|
+
{
|
|
394
|
+
"permitted": {
|
|
395
|
+
"token": "0x833589fcd6edb6e08f4c7c32d4f71b54bda02913",
|
|
396
|
+
"amount": "25000000"
|
|
397
|
+
},
|
|
398
|
+
"spender": "0x60AB9E090abF9B6BcFbA16017eE18AEf5f2c2289",
|
|
399
|
+
"nonce": "123456...",
|
|
400
|
+
"deadline": "1725561600"
|
|
401
|
+
}
|
|
402
|
+
```
|
|
403
|
+
|
|
404
|
+
The `spender` must be in the signed message but is **not** included in the JSON `permit` object sent to `/solve`.
|
|
405
|
+
|
|
406
|
+
### Step 5 — Call `/api/v1/solve`
|
|
407
|
+
|
|
408
|
+
Send the `permit` (without `spender`) and the 65-byte hex `signature`.
|
|
409
|
+
|
|
410
|
+
---
|
|
411
|
+
|
|
412
|
+
## Error & retry guide
|
|
413
|
+
|
|
414
|
+
| HTTP / `ok` | Likely cause | Recommended action |
|
|
415
|
+
| --- | --- | --- |
|
|
416
|
+
| `400` / `ok:false` | Invalid request shape | Fix the JSON, check `fromDecimals`, `toDecimals`, `chain` |
|
|
417
|
+
| `401` / `Invalid or revoked API key` | Bad/missing key or rate limit | Check `X-ORKID-API-Key` header, request a key rotation |
|
|
418
|
+
| `403` / `Missing anti-scraping token` | Browser request without page token | Use an API key instead |
|
|
419
|
+
| `200` / `error: "Swap too small..."` | Below gasless minimum | Increase amount or set `dryRun: true` and have user pay gas |
|
|
420
|
+
| `200` / `error: "Gas estimation failed"` | Permit/signature/calldata will revert | Check token approval, nonce, spender, chain, token addresses, balance |
|
|
421
|
+
| `200` / `error: "Invalid nonce"` | Nonce already used | Call `findUnusedNonce` again |
|
|
422
|
+
| `200` / `error: "TRANSFER_FROM_FAILED"` | Insufficient allowance or balance | Check `token.approve(PERMIT2, ...)` and `balanceOf` |
|
|
423
|
+
| `200` / `error: "Price impact too high..."` | Slippage exceeds max (500 bps) | Try a smaller amount or a different token pair |
|
|
424
|
+
|
|
425
|
+
### Retry rules
|
|
426
|
+
|
|
427
|
+
- Retry `5xx` up to 3 times with exponential backoff.
|
|
428
|
+
- Do not blindly retry a successful `/solve` (`ok: true`) — it may have already submitted.
|
|
429
|
+
- For `dryRun: false`, if the solver returns `ok: true` with `transaction.txHash`, the swap is submitted.
|
|
430
|
+
- For `dryRun: true`, cache the returned `data` and submit it with `sendTransaction` only once.
|
|
431
|
+
- In the sandbox, retries are safe — no transaction is ever submitted.
|
|
432
|
+
|
|
433
|
+
---
|
|
434
|
+
|
|
435
|
+
## SDK reference
|
|
436
|
+
|
|
437
|
+
### Install
|
|
438
|
+
|
|
439
|
+
```bash
|
|
440
|
+
npm install @orkid/sdk
|
|
441
|
+
# or copy the source files into your project
|
|
442
|
+
```
|
|
443
|
+
|
|
444
|
+
Peer dependencies (only needed for signing):
|
|
445
|
+
|
|
446
|
+
```bash
|
|
447
|
+
npm install viem
|
|
448
|
+
# or
|
|
449
|
+
npm install ethers
|
|
450
|
+
```
|
|
451
|
+
|
|
452
|
+
### Quick start — production
|
|
453
|
+
|
|
454
|
+
```typescript
|
|
455
|
+
import { OrkidClient, OrkidPermitSigner } from '@orkid/sdk'
|
|
456
|
+
import { createWalletClient, http } from 'viem'
|
|
457
|
+
import { base } from 'viem/chains'
|
|
458
|
+
import { privateKeyToAccount } from 'viem/accounts'
|
|
459
|
+
|
|
460
|
+
const client = new OrkidClient({
|
|
461
|
+
apiKey: process.env.ORKID_API_KEY!,
|
|
462
|
+
// baseUrl defaults to https://orkidlabs.xyz
|
|
463
|
+
})
|
|
464
|
+
|
|
465
|
+
const account = privateKeyToAccount('0x...')
|
|
466
|
+
const walletClient = createWalletClient({ account, chain: base, transport: http() })
|
|
467
|
+
|
|
468
|
+
const permitSigner = new OrkidPermitSigner(walletClient)
|
|
469
|
+
|
|
470
|
+
async function swapUsdcToWeth() {
|
|
471
|
+
const quote = await client.getQuote({
|
|
472
|
+
from: 'USDC',
|
|
473
|
+
to: 'WETH',
|
|
474
|
+
amount: '25.0',
|
|
475
|
+
chain: 'base',
|
|
476
|
+
})
|
|
477
|
+
|
|
478
|
+
if (!quote.ok) throw new Error(quote.error)
|
|
479
|
+
|
|
480
|
+
const signed = await permitSigner.prepareAndSign({
|
|
481
|
+
fromToken: '0x833589fcd6edb6e08f4c7c32d4f71b54bda02913',
|
|
482
|
+
fromDecimals: 6,
|
|
483
|
+
amount: '25.0',
|
|
484
|
+
chain: 'base',
|
|
485
|
+
})
|
|
486
|
+
|
|
487
|
+
const result = await client.solve({
|
|
488
|
+
from: 'USDC',
|
|
489
|
+
to: 'WETH',
|
|
490
|
+
amount: '25.0',
|
|
491
|
+
chain: 'base',
|
|
492
|
+
user: account.address,
|
|
493
|
+
fromAddress: '0x833589fcd6edb6e08f4c7c32d4f71b54bda02913',
|
|
494
|
+
fromDecimals: 6,
|
|
495
|
+
toAddress: '0x4200000000000000000000000000000000000006',
|
|
496
|
+
toDecimals: 18,
|
|
497
|
+
dryRun: false,
|
|
498
|
+
permit: signed.permit,
|
|
499
|
+
signature: signed.signature,
|
|
500
|
+
})
|
|
501
|
+
|
|
502
|
+
if (!result.ok) throw new Error(result.error)
|
|
503
|
+
console.log('txHash', result.transaction?.txHash)
|
|
504
|
+
}
|
|
505
|
+
```
|
|
506
|
+
|
|
507
|
+
### Quick start — sandbox
|
|
508
|
+
|
|
509
|
+
```typescript
|
|
510
|
+
import { OrkidClient, OrkidPermitSigner, SANDBOX_BASE_URL } from '@orkid/sdk'
|
|
511
|
+
|
|
512
|
+
const sandbox = new OrkidClient({
|
|
513
|
+
apiKey: process.env.ORKID_SANDBOX_KEY!,
|
|
514
|
+
baseUrl: SANDBOX_BASE_URL, // https://sandbox.orkidlabs.com
|
|
515
|
+
})
|
|
516
|
+
|
|
517
|
+
// Same API surface — quotes are real, solves are always dry-run
|
|
518
|
+
const quote = await sandbox.getQuote({
|
|
519
|
+
from: 'USDC',
|
|
520
|
+
to: 'WETH',
|
|
521
|
+
amount: '25.0',
|
|
522
|
+
chain: 'arbitrum',
|
|
523
|
+
})
|
|
524
|
+
```
|
|
525
|
+
|
|
526
|
+
### Exports
|
|
527
|
+
|
|
528
|
+
| Export | Description |
|
|
529
|
+
| --- | --- |
|
|
530
|
+
| `OrkidClient` | Main API client — `getQuote`, `solve`, `confirmTransaction`, `getAccount`, `getUsage`, `getRebates` |
|
|
531
|
+
| `SANDBOX_BASE_URL` | `'https://sandbox.orkidlabs.com'` — pass as `baseUrl` for the sandbox |
|
|
532
|
+
| `OrkidPermitSigner` | Permit2 signing helper (viem and ethers variants) |
|
|
533
|
+
| `ORKID_CHAIN_CONFIG` | Per-chain metadata (TVMExecutor, Permit2, RPC, explorer, min notional) |
|
|
534
|
+
| `PERMIT2` | Canonical Permit2 address: `0x000000000022D473030F116dDEE9F6B43aC78BA3` |
|
|
535
|
+
|
|
536
|
+
---
|
|
537
|
+
|
|
538
|
+
## CLI
|
|
539
|
+
|
|
540
|
+
The package ships an `orkid` binary for testing integration without writing code:
|
|
541
|
+
|
|
542
|
+
```bash
|
|
543
|
+
# via npx once published, or `node dist/cli.js` inside this repo
|
|
544
|
+
export ORKID_API_KEY=<your-40-hex-key>
|
|
545
|
+
# export ORKID_API_URL=https://orkidlabs.xyz # optional override
|
|
546
|
+
|
|
547
|
+
orkid quote --from USDC --to WETH --amount 25 --chain base
|
|
548
|
+
orkid tokens --chain base --search USDC
|
|
549
|
+
orkid account # partner account info + rebate rate
|
|
550
|
+
orkid usage # your account's usage events
|
|
551
|
+
orkid usage --event-type solve --period 2026-09
|
|
552
|
+
orkid rebates # your accrued monthly rebates
|
|
553
|
+
orkid rebates --period 2026-09
|
|
554
|
+
orkid status # API health check
|
|
555
|
+
```
|
|
556
|
+
|
|
557
|
+
Global flags: `--api-key`, `--base-url`, `--json` (raw JSON output), `-h/--help`.
|
|
558
|
+
|
|
559
|
+
To target the sandbox, pass `--base-url https://sandbox.orkidlabs.com`:
|
|
560
|
+
|
|
561
|
+
```bash
|
|
562
|
+
orkid --base-url https://sandbox.orkidlabs.com --api-key <sandbox-key> status
|
|
563
|
+
orkid --base-url https://sandbox.orkidlabs.com --api-key <sandbox-key> quote --from USDC --to WETH --amount 25 --chain base
|
|
564
|
+
```
|
|
565
|
+
|
|
566
|
+
Live solve requires a funded wallet with a Permit2 allowance and `viem` installed:
|
|
567
|
+
|
|
568
|
+
```bash
|
|
569
|
+
npm i viem
|
|
570
|
+
export ORKID_PRIVATE_KEY=0x...
|
|
571
|
+
orkid solve --from USDC --to WETH --amount 25 \
|
|
572
|
+
--from-address 0x833589fcd6edb6e08f4c7c32d4f71b54bda02913 --from-decimals 6 \
|
|
573
|
+
--to-address 0x4200000000000000000000000000000000000006 --to-decimals 18
|
|
574
|
+
# default is a dry-run — pass --execute to submit the live swap
|
|
575
|
+
```
|
|
576
|
+
|
|
577
|
+
---
|
|
578
|
+
|
|
579
|
+
## Reporting endpoints
|
|
580
|
+
|
|
581
|
+
Partners can query their own account, usage, and rebate data with the same `X-ORKID-API-Key` header. All responses are scoped to the account that owns the key — a key can never see another partner's data.
|
|
582
|
+
|
|
583
|
+
> **Sandbox note:** These endpoints return empty/zero results on the sandbox because sandbox traffic is not tracked.
|
|
584
|
+
|
|
585
|
+
### `GET /api/v1/account`
|
|
586
|
+
|
|
587
|
+
Returns the partner account, including the configured rebate rate.
|
|
588
|
+
|
|
589
|
+
```bash
|
|
590
|
+
curl -H "X-ORKID-API-Key: <key>" https://orkidlabs.xyz/api/v1/account
|
|
591
|
+
```
|
|
592
|
+
|
|
593
|
+
```json
|
|
594
|
+
{ "ok": true, "account": { "id": "…", "slug": "alternatefutures", "name": "Alternate Futures", "rebate_bps": 3, "rebate_active": true } }
|
|
595
|
+
```
|
|
596
|
+
|
|
597
|
+
### `GET /api/v1/usage`
|
|
598
|
+
|
|
599
|
+
Lists usage events (quotes and solves) recorded for the account. Optional filters: `?period=YYYY-MM`, `?event_type=route|solve`, `?limit=N` (default 100).
|
|
600
|
+
|
|
601
|
+
```bash
|
|
602
|
+
curl -H "X-ORKID-API-Key: <key>" "https://orkidlabs.xyz/api/v1/usage?event_type=solve&period=2026-09"
|
|
603
|
+
```
|
|
604
|
+
|
|
605
|
+
```json
|
|
606
|
+
{ "ok": true, "events": [ { "event_type": "solve", "volume_usd": 25, "is_dry_run": true, "created_at": "…" } ], "total_volume_usd": 25 }
|
|
607
|
+
```
|
|
608
|
+
|
|
609
|
+
### `GET /api/v1/rebates`
|
|
610
|
+
|
|
611
|
+
Lists monthly rebate ledger entries. Optional filter: `?period=YYYY-MM`.
|
|
612
|
+
|
|
613
|
+
```bash
|
|
614
|
+
curl -H "X-ORKID-API-Key: <key>" "https://orkidlabs.xyz/api/v1/rebates?period=2026-09"
|
|
615
|
+
```
|
|
616
|
+
|
|
617
|
+
```json
|
|
618
|
+
{ "ok": true, "rebates": [ { "period": "monthly", "period_start": "2026-09-01T00:00:00+00:00", "volume_usd": 100, "rebate_bps": 3, "rebate_usd": 0.03, "status": "accrued" } ] }
|
|
619
|
+
```
|
|
620
|
+
|
|
621
|
+
Rebate volume counts only **non-dry-run, non-test `solve` events** — quote requests, dry runs, and operator test traffic (`is_test: true`) are excluded. `rebate_usd = volume_usd × rebate_bps / 10,000`. Entries move from `accrued` → `paid` when Orkid settles monthly.
|
|
622
|
+
|
|
623
|
+
### Referral rebates
|
|
624
|
+
|
|
625
|
+
Partners who source other partners earn a referral cut of the referred account's volume, on top of their own. Referral income appears in `GET /api/v1/rebates` as separate ledger rows:
|
|
626
|
+
|
|
627
|
+
```json
|
|
628
|
+
{ "kind": "referral", "source_account_id": "<referred-account-uuid>", "volume_usd": 1000, "rebate_bps": 1, "rebate_usd": 0.10 }
|
|
629
|
+
```
|
|
630
|
+
|
|
631
|
+
`kind: "volume"` rows are your own volume; `kind: "referral"` rows pay you for a referred partner's volume (`source_account_id` identifies which one). Example split: a referred partner at 2 bps + 1 bps referral keeps total cost at 3 bps — the sourcer earns from their network's activity.
|
|
632
|
+
|
|
633
|
+
### `GET /api/v1/usage` — event flags
|
|
634
|
+
|
|
635
|
+
Each event carries `is_dry_run` (encode-only, not executed) and `is_test` (operator-marked test traffic). Only `is_dry_run=false, is_test=false` solve events count toward rebates.
|
|
636
|
+
|
|
637
|
+
### User-pays-gas swaps (below the gasless minimum)
|
|
638
|
+
|
|
639
|
+
Quotes and solves below the gasless floor still work — the response carries `gaslessEligible: false` and `/api/v1/solve` returns encode-only calldata. The client submits the transaction itself (the user's wallet pays gas).
|
|
640
|
+
|
|
641
|
+
To count that settled volume toward rebates, the client **must call `POST /api/v1/confirm`** once the tx is mined:
|
|
642
|
+
|
|
643
|
+
```bash
|
|
644
|
+
curl -X POST https://orkidlabs.xyz/api/v1/confirm \
|
|
645
|
+
-H "X-ORKID-API-Key: <key>" -H "Content-Type: application/json" \
|
|
646
|
+
-d '{"txHash": "0x…", "chain": "base"}'
|
|
647
|
+
```
|
|
648
|
+
|
|
649
|
+
The API verifies on-chain that the transaction succeeded, targeted the TVMExecutor, and matches the exact calldata it encoded (`keccak256` of `tx.input`). Self-reported hashes that don't match an encoded swap are rejected. The SDK (`client.confirmTransaction`), CLI (`orkid confirm <txHash>`, automatic after `solve --execute` fallbacks), and widget (automatic) call this for you — hand-rolled integrations must call it explicitly or below-min volume won't be tracked.
|
|
650
|
+
|
|
651
|
+
---
|
|
652
|
+
|
|
653
|
+
## Examples
|
|
654
|
+
|
|
655
|
+
See the `examples/` directory for complete, runnable examples:
|
|
656
|
+
|
|
657
|
+
- `examples/vanilla-viem.ts` — full flow with a viem wallet client
|
|
658
|
+
- `examples/vanilla-ethers.ts` — full flow with an ethers signer
|
|
659
|
+
- `examples/react.tsx` — React hook wrapper
|
|
660
|
+
- `examples/nextjs-server-action.ts` — server-side key protection in a Next.js app
|
|
661
|
+
- `examples/curl.sh` — copy-paste curl commands
|
|
662
|
+
|
|
663
|
+
---
|
|
664
|
+
|
|
665
|
+
## OpenAPI
|
|
666
|
+
|
|
667
|
+
The full OpenAPI 3.0 spec is in `openapi.yaml` and can be imported into Postman, Swagger UI, or used to generate client SDKs.
|
|
668
|
+
|
|
669
|
+
---
|
|
670
|
+
|
|
671
|
+
## Minimum notional by chain
|
|
672
|
+
|
|
673
|
+
| Chain | Minimum USD | Notes |
|
|
674
|
+
| --- | --- | --- |
|
|
675
|
+
| Base | $20 | Low L1 data fee, cheap gas |
|
|
676
|
+
| Ethereum | $200 | MEV-protected via Flashbots Protect |
|
|
677
|
+
| Arbitrum | $50 | Low fees, fast settlement |
|
|
678
|
+
| Polygon | $50 | Low fees, PoS |
|
|
679
|
+
|
|
680
|
+
Below the minimum, swaps still work but the user pays gas (the solver returns encode-only calldata via `dryRun` semantics — see [User-pays-gas swaps](#user-pays-gas-swaps-below-the-gasless-minimum)).
|
|
681
|
+
|
|
682
|
+
---
|
|
683
|
+
|
|
684
|
+
## Contact
|
|
685
|
+
|
|
686
|
+
For an API key, sandbox access, higher rate limits, or custom fee arrangements:
|
|
687
|
+
|
|
688
|
+
- Email: `api@orkidlabs.com`
|
|
689
|
+
- Dashboard: `https://orkidlabs.xyz/board/admin` (master accounts only)
|