@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/dist/cli.js ADDED
@@ -0,0 +1,856 @@
1
+ #!/usr/bin/env node
2
+ "use strict";
3
+ var __create = Object.create;
4
+ var __defProp = Object.defineProperty;
5
+ var __getOwnPropDesc = Object.getOwnPropertyDescriptor;
6
+ var __getOwnPropNames = Object.getOwnPropertyNames;
7
+ var __getProtoOf = Object.getPrototypeOf;
8
+ var __hasOwnProp = Object.prototype.hasOwnProperty;
9
+ var __esm = (fn, res) => function __init() {
10
+ return fn && (res = (0, fn[__getOwnPropNames(fn)[0]])(fn = 0)), res;
11
+ };
12
+ var __export = (target, all) => {
13
+ for (var name in all)
14
+ __defProp(target, name, { get: all[name], enumerable: true });
15
+ };
16
+ var __copyProps = (to, from, except, desc) => {
17
+ if (from && typeof from === "object" || typeof from === "function") {
18
+ for (let key of __getOwnPropNames(from))
19
+ if (!__hasOwnProp.call(to, key) && key !== except)
20
+ __defProp(to, key, { get: () => from[key], enumerable: !(desc = __getOwnPropDesc(from, key)) || desc.enumerable });
21
+ }
22
+ return to;
23
+ };
24
+ var __toESM = (mod, isNodeMode, target) => (target = mod != null ? __create(__getProtoOf(mod)) : {}, __copyProps(
25
+ // If the importer is in node compatibility mode or this is not an ESM
26
+ // file that has been converted to a CommonJS file using a Babel-
27
+ // compatible transform (i.e. "__esModule" has not been set), then set
28
+ // "default" to the CommonJS "module.exports" for node compatibility.
29
+ isNodeMode || !mod || !mod.__esModule ? __defProp(target, "default", { value: mod, enumerable: true }) : target,
30
+ mod
31
+ ));
32
+
33
+ // src/chains.ts
34
+ var chains_exports = {};
35
+ __export(chains_exports, {
36
+ ORKID_CHAINS: () => ORKID_CHAINS,
37
+ ORKID_CHAIN_CONFIG: () => ORKID_CHAIN_CONFIG,
38
+ PERMIT2: () => PERMIT2,
39
+ chainConfigFromName: () => chainConfigFromName,
40
+ chainIdFromName: () => chainIdFromName,
41
+ chainNameFromId: () => chainNameFromId,
42
+ getPermit2Address: () => getPermit2Address,
43
+ normalizeChainName: () => normalizeChainName
44
+ });
45
+ function getPermit2Address() {
46
+ return PERMIT2;
47
+ }
48
+ function chainIdFromName(name) {
49
+ const normalized = normalizeChainName(name);
50
+ for (const config of Object.values(ORKID_CHAIN_CONFIG)) {
51
+ if (config.name === normalized) return config.id;
52
+ }
53
+ throw new Error(`Unsupported chain: ${name}`);
54
+ }
55
+ function chainNameFromId(chainId) {
56
+ const config = ORKID_CHAIN_CONFIG[chainId];
57
+ if (!config) throw new Error(`Unsupported chain id: ${chainId}`);
58
+ return config.name;
59
+ }
60
+ function chainConfigFromName(name) {
61
+ const normalized = normalizeChainName(name);
62
+ const config = Object.values(ORKID_CHAIN_CONFIG).find((c) => c.name === normalized);
63
+ if (!config) throw new Error(`Unsupported chain: ${name}`);
64
+ return config;
65
+ }
66
+ function normalizeChainName(name) {
67
+ const n = name.toLowerCase();
68
+ if (n === "mainnet") return "ethereum";
69
+ return n;
70
+ }
71
+ var PERMIT2, ORKID_CHAIN_CONFIG, ORKID_CHAINS;
72
+ var init_chains = __esm({
73
+ "src/chains.ts"() {
74
+ "use strict";
75
+ PERMIT2 = "0x000000000022D473030F116dDEE9F6B43aC78BA3";
76
+ ORKID_CHAIN_CONFIG = {
77
+ 8453: {
78
+ id: 8453,
79
+ name: "base",
80
+ shortName: "BASE",
81
+ color: "#0052FF",
82
+ permit2: PERMIT2,
83
+ tvmExecutor: "0x60AB9E090abF9B6BcFbA16017eE18AEf5f2c2289",
84
+ sandboxTvmExecutor: "0xD5B87788AF6F9E04E7FA298Bd973cEEA8c0Df0aF",
85
+ tychoRouter: "0x2D3524b9b5dAE34B646614eebb1E038D403E4Cac",
86
+ explorer: "https://basescan.org",
87
+ rpcUrl: "https://rpc.orkidlabs.com/rpc/base",
88
+ nativeSymbol: "ETH",
89
+ nativeDecimals: 18,
90
+ minNotionalUsd: 20,
91
+ isLive: true
92
+ },
93
+ 1: {
94
+ id: 1,
95
+ name: "ethereum",
96
+ shortName: "ETH",
97
+ color: "#627EEA",
98
+ permit2: PERMIT2,
99
+ tvmExecutor: "0xCfc33b521190FcD414c8f81f479749c4dCE8f69b",
100
+ sandboxTvmExecutor: "0xd1b5c6e142fd95be389cc4ca117cb0fb7429534f",
101
+ tychoRouter: "0xfd0b31d2e955fa55e3fa641fe90e08b677188d35",
102
+ explorer: "https://etherscan.io",
103
+ rpcUrl: "https://rpc.orkidlabs.com/rpc/ethereum",
104
+ nativeSymbol: "ETH",
105
+ nativeDecimals: 18,
106
+ minNotionalUsd: 200,
107
+ isLive: true
108
+ },
109
+ 130: {
110
+ id: 130,
111
+ name: "unichain",
112
+ shortName: "UNI",
113
+ color: "#FF007A",
114
+ permit2: PERMIT2,
115
+ tvmExecutor: "0x3fC25b3Ae57514e839f901cB1f628F8557150C2b",
116
+ sandboxTvmExecutor: "0x0000000000000000000000000000000000000000",
117
+ tychoRouter: "0xFfA5ec2e444e4285108e4a17b82dA495c178427B",
118
+ explorer: "https://unichain.blockscout.com",
119
+ rpcUrl: "https://unichain-rpc.publicnode.com",
120
+ nativeSymbol: "ETH",
121
+ nativeDecimals: 18,
122
+ minNotionalUsd: 200,
123
+ isLive: false
124
+ },
125
+ 42161: {
126
+ id: 42161,
127
+ name: "arbitrum",
128
+ shortName: "ARB",
129
+ color: "#28A0F0",
130
+ permit2: PERMIT2,
131
+ tvmExecutor: "0xf57235609bf99fb0b017e95b3bc33a606a541374",
132
+ sandboxTvmExecutor: "0x9a4cd4ce4ca1dd554d337270b3353da9abb52c83",
133
+ tychoRouter: "0x924F147c50eA59f5180a26031A8b65B2aA1e81Cd",
134
+ explorer: "https://arbiscan.io",
135
+ rpcUrl: "https://rpc.orkidlabs.com/rpc/arbitrum",
136
+ nativeSymbol: "ETH",
137
+ nativeDecimals: 18,
138
+ minNotionalUsd: 50,
139
+ isLive: true
140
+ },
141
+ 137: {
142
+ id: 137,
143
+ name: "polygon",
144
+ shortName: "POL",
145
+ color: "#8247E5",
146
+ permit2: PERMIT2,
147
+ tvmExecutor: "0xd633Ea84E6E2Db002C14C6fe3A064eE2cc4E258c",
148
+ sandboxTvmExecutor: "0x9a4cd4ce4ca1dd554d337270b3353da9abb52c83",
149
+ tychoRouter: "0xbd4e6011F03355C2A377Fd9Af939322A7d0A1bC1",
150
+ explorer: "https://polygonscan.com",
151
+ rpcUrl: "https://rpc.orkidlabs.com/rpc/polygon",
152
+ nativeSymbol: "MATIC",
153
+ nativeDecimals: 18,
154
+ minNotionalUsd: 50,
155
+ isLive: true
156
+ }
157
+ };
158
+ ORKID_CHAINS = ["base", "ethereum", "arbitrum", "polygon"];
159
+ }
160
+ });
161
+
162
+ // src/permit.ts
163
+ function buildPermit2Domain(chainId) {
164
+ return {
165
+ name: "Permit2",
166
+ chainId,
167
+ verifyingContract: getPermit2Address()
168
+ };
169
+ }
170
+ function buildPermitMessage(params) {
171
+ return {
172
+ permitted: {
173
+ token: params.token,
174
+ amount: params.amount
175
+ },
176
+ spender: params.spender,
177
+ nonce: params.nonce,
178
+ deadline: params.deadline
179
+ };
180
+ }
181
+ function toOrkidPermit(message) {
182
+ return {
183
+ permitted: message.permitted,
184
+ nonce: message.nonce,
185
+ deadline: message.deadline
186
+ };
187
+ }
188
+ function parseAmount(value, decimals) {
189
+ const normalized = value.trim();
190
+ if (!/^\d+(\.\d+)?$/.test(normalized)) {
191
+ throw new Error(`Invalid amount: ${value}`);
192
+ }
193
+ const [whole, fraction = ""] = normalized.split(".");
194
+ if (fraction.length > decimals) {
195
+ throw new Error(`Amount ${value} exceeds ${decimals} decimals`);
196
+ }
197
+ const scale = BigInt("1" + "0".repeat(decimals));
198
+ const wholeBig = BigInt(whole) * scale;
199
+ const fractionBig = BigInt(fraction.padEnd(decimals, "0") || "0");
200
+ return (wholeBig + fractionBig).toString();
201
+ }
202
+ function formatAmount(raw, decimals) {
203
+ const rawBig = BigInt(raw);
204
+ const scale = BigInt("1" + "0".repeat(decimals));
205
+ const whole = (rawBig / scale).toString();
206
+ const fraction = (rawBig % scale).toString().padStart(decimals, "0");
207
+ const trimmed = fraction.replace(/0+$/, "");
208
+ return trimmed ? `${whole}.${trimmed}` : whole;
209
+ }
210
+ function buildSwapPermit(chainName, token, amount, nonce, deadlineSeconds) {
211
+ const chainConfig = chainConfigFromName(chainName);
212
+ const message = buildPermitMessage({
213
+ token,
214
+ amount,
215
+ nonce,
216
+ deadline: String(deadlineSeconds),
217
+ spender: chainConfig.tvmExecutor
218
+ });
219
+ const permit = toOrkidPermit(message);
220
+ return { message, permit, chainConfig };
221
+ }
222
+ function defaultDeadline() {
223
+ return Math.floor(Date.now() / 1e3) + 3600;
224
+ }
225
+ function buildNonce(wordPos, bitPos) {
226
+ return (wordPos << BigInt(8) | BigInt(bitPos)).toString();
227
+ }
228
+ var PERMIT2_ABI;
229
+ var init_permit = __esm({
230
+ "src/permit.ts"() {
231
+ "use strict";
232
+ init_chains();
233
+ PERMIT2_ABI = [
234
+ {
235
+ type: "function",
236
+ name: "nonceBitmap",
237
+ inputs: [
238
+ { name: "owner", type: "address" },
239
+ { name: "wordPos", type: "uint256" }
240
+ ],
241
+ outputs: [{ name: "", type: "uint256" }],
242
+ stateMutability: "view"
243
+ }
244
+ ];
245
+ }
246
+ });
247
+
248
+ // src/viem.ts
249
+ var viem_exports = {};
250
+ __export(viem_exports, {
251
+ OrkidViemPermitSigner: () => OrkidViemPermitSigner,
252
+ buildNonce: () => buildNonce,
253
+ buildPermit2Domain: () => buildPermit2Domain,
254
+ buildPermitMessage: () => buildPermitMessage,
255
+ buildSwapPermit: () => buildSwapPermit,
256
+ defaultDeadline: () => defaultDeadline,
257
+ formatAmount: () => formatAmount,
258
+ parseAmount: () => parseAmount,
259
+ toOrkidPermit: () => toOrkidPermit
260
+ });
261
+ function randomStartWord() {
262
+ if (typeof crypto !== "undefined" && crypto.getRandomValues) {
263
+ const arr = new Uint32Array(1);
264
+ crypto.getRandomValues(arr);
265
+ return BigInt(arr[0] >>> 8);
266
+ }
267
+ return BigInt(Math.floor(Math.random() * 16777215));
268
+ }
269
+ var OrkidViemPermitSigner;
270
+ var init_viem = __esm({
271
+ "src/viem.ts"() {
272
+ "use strict";
273
+ init_chains();
274
+ init_permit();
275
+ init_permit();
276
+ init_permit();
277
+ OrkidViemPermitSigner = class {
278
+ walletClient;
279
+ publicClient;
280
+ constructor(walletClient, publicClient) {
281
+ this.walletClient = walletClient;
282
+ this.publicClient = publicClient;
283
+ }
284
+ async findUnusedNonce(owner, maxWords = 1e3) {
285
+ const startWord = randomStartWord();
286
+ const max = BigInt(maxWords);
287
+ for (let offset = BigInt(0); offset < max; offset++) {
288
+ const wordPos = (startWord + offset) % BigInt(1e6);
289
+ const bitmap = await this.publicClient.readContract({
290
+ address: "0x000000000022D473030F116dDEE9F6B43aC78BA3",
291
+ abi: PERMIT2_ABI,
292
+ functionName: "nonceBitmap",
293
+ args: [owner, wordPos]
294
+ });
295
+ const bitmapBig = BigInt(String(bitmap));
296
+ if (bitmapBig === (BigInt(1) << BigInt(256)) - BigInt(1)) continue;
297
+ for (let bitPos = 0; bitPos < 256; bitPos++) {
298
+ const bit = BigInt(1) << BigInt(bitPos);
299
+ if ((bitmapBig & bit) === BigInt(0)) {
300
+ return buildNonce(wordPos, bitPos);
301
+ }
302
+ }
303
+ }
304
+ throw new Error("No unused Permit2 nonce found after scanning 1000 words");
305
+ }
306
+ async signSwap(params) {
307
+ const chainConfig = chainConfigFromName(params.chain);
308
+ const amountRaw = parseAmount(params.amount, params.fromDecimals);
309
+ const nonce = params.nonce || await this.findUnusedNonce(params.user);
310
+ const deadline = params.deadline || defaultDeadline();
311
+ const message = buildPermitMessage({
312
+ token: params.fromToken,
313
+ amount: amountRaw,
314
+ nonce,
315
+ deadline: String(deadline),
316
+ spender: chainConfig.tvmExecutor
317
+ });
318
+ const domain = buildPermit2Domain(chainConfig.id);
319
+ const account = this.walletClient.account ?? params.user;
320
+ const signature = await this.walletClient.signTypedData({
321
+ account,
322
+ domain,
323
+ types: {
324
+ PermitTransferFrom: [
325
+ { name: "permitted", type: "TokenPermissions" },
326
+ { name: "spender", type: "address" },
327
+ { name: "nonce", type: "uint256" },
328
+ { name: "deadline", type: "uint256" }
329
+ ],
330
+ TokenPermissions: [
331
+ { name: "token", type: "address" },
332
+ { name: "amount", type: "uint256" }
333
+ ]
334
+ },
335
+ primaryType: "PermitTransferFrom",
336
+ message: {
337
+ permitted: {
338
+ token: message.permitted.token,
339
+ amount: BigInt(message.permitted.amount)
340
+ },
341
+ spender: message.spender,
342
+ nonce: BigInt(message.nonce),
343
+ deadline: BigInt(message.deadline)
344
+ }
345
+ });
346
+ return {
347
+ permit: toOrkidPermit(message),
348
+ signature,
349
+ chainConfig
350
+ };
351
+ }
352
+ /**
353
+ * Read the ERC20 allowance of the user for the Permit2 contract.
354
+ */
355
+ async getPermit2Allowance(token, owner) {
356
+ const allowance = await this.publicClient.readContract({
357
+ address: token,
358
+ abi: [
359
+ {
360
+ type: "function",
361
+ name: "allowance",
362
+ inputs: [
363
+ { name: "owner", type: "address" },
364
+ { name: "spender", type: "address" }
365
+ ],
366
+ outputs: [{ name: "", type: "uint256" }],
367
+ stateMutability: "view"
368
+ }
369
+ ],
370
+ functionName: "allowance",
371
+ args: [owner, "0x000000000022D473030F116dDEE9F6B43aC78BA3"]
372
+ });
373
+ return BigInt(String(allowance));
374
+ }
375
+ };
376
+ }
377
+ });
378
+
379
+ // src/client.ts
380
+ var DEFAULT_BASE_URL = "https://orkidlabs.xyz";
381
+ var OrkidClient = class {
382
+ apiKey;
383
+ operatorSecret;
384
+ baseUrl;
385
+ fetch;
386
+ constructor(options) {
387
+ this.apiKey = options.apiKey;
388
+ this.operatorSecret = options.operatorSecret;
389
+ this.baseUrl = (options.baseUrl || DEFAULT_BASE_URL).replace(/\/$/, "");
390
+ this.fetch = options.fetch || globalThis.fetch.bind(globalThis);
391
+ if (this.apiKey && !/^[a-f0-9]{40}$/i.test(this.apiKey)) {
392
+ throw new Error("OrkidClient: apiKey must be a 40-character hex string");
393
+ }
394
+ }
395
+ baseHeaders(json) {
396
+ const headers = {};
397
+ if (json) headers["Content-Type"] = "application/json";
398
+ if (this.apiKey) headers["X-ORKID-API-Key"] = this.apiKey;
399
+ if (this.operatorSecret) headers["X-ORKID-Operator"] = this.operatorSecret;
400
+ return headers;
401
+ }
402
+ /**
403
+ * Anonymous (keyless) browser calls can hit 403 when the anti-scrape token
404
+ * is missing or expired. Recover by hitting /api/v1/handshake (issues a
405
+ * fresh bound cookie) and retrying once.
406
+ */
407
+ async fetchWithHandshake(doFetch) {
408
+ let res = await doFetch();
409
+ if (res.status === 403 && !this.apiKey) {
410
+ const body = await res.clone().json().catch(() => null);
411
+ if (body && typeof body.error === "string" && /anti-scraping token/i.test(body.error)) {
412
+ try {
413
+ await this.fetch(`${this.baseUrl}/api/v1/handshake`, { credentials: "include" });
414
+ } catch {
415
+ }
416
+ res = await doFetch();
417
+ }
418
+ }
419
+ return res;
420
+ }
421
+ /**
422
+ * Get an executable quote for a swap.
423
+ */
424
+ async getQuote(request) {
425
+ return this.post("/api/v1/route", request);
426
+ }
427
+ /**
428
+ * Solve a swap gaslessly.
429
+ */
430
+ async solve(request) {
431
+ return this.post("/api/v1/solve", request);
432
+ }
433
+ /**
434
+ * Convenience wrapper: get a quote then dry-run solve the same swap with a
435
+ * pre-signed permit. Useful for testing signatures before live execution.
436
+ */
437
+ async dryRun(request) {
438
+ return this.solve({ ...request, dryRun: true });
439
+ }
440
+ /**
441
+ * Fetch a list of tokens for a chain from the Orkid token search endpoint.
442
+ * This is a convenience; the API key is optional for this read-only endpoint.
443
+ */
444
+ async listTokens(params = { chain: "base" }) {
445
+ const url = new URL(`${this.baseUrl}/api/v1/tokens`);
446
+ url.searchParams.set("chain", params.chain);
447
+ if (params.search) url.searchParams.set("search", params.search);
448
+ if (params.limit) url.searchParams.set("limit", String(params.limit));
449
+ const headers = this.baseHeaders(false);
450
+ const res = await this.fetchWithHandshake(() => this.fetch(url.toString(), { headers }));
451
+ if (!res.ok) {
452
+ const text = await res.text().catch(() => "");
453
+ return { ok: false, error: `HTTP ${res.status}: ${text}` };
454
+ }
455
+ return res.json();
456
+ }
457
+ /**
458
+ * Fetch the partner account associated with this API key.
459
+ */
460
+ async getAccount() {
461
+ return this.get("/api/v1/account");
462
+ }
463
+ /**
464
+ * Fetch usage events for the partner account. Optionally filter by month
465
+ * (`period: 'YYYY-MM'`) and event type (`'route'` | `'solve'`).
466
+ */
467
+ async getUsage(params = {}) {
468
+ const query = {};
469
+ if (params.period) query.period = params.period;
470
+ if (params.eventType) query.event_type = params.eventType;
471
+ if (params.limit) query.limit = String(params.limit);
472
+ return this.get("/api/v1/usage", query);
473
+ }
474
+ /**
475
+ * Fetch the rebate ledger for the partner account. Optionally filter by
476
+ * month (`period: 'YYYY-MM'`).
477
+ */
478
+ async getRebates(params = {}) {
479
+ const query = {};
480
+ if (params.period) query.period = params.period;
481
+ return this.get("/api/v1/rebates", query);
482
+ }
483
+ /**
484
+ * Health check on the API. The API key is optional here.
485
+ */
486
+ async getStatus() {
487
+ return this.get("/api/v1/status");
488
+ }
489
+ /**
490
+ * Confirm a user-submitted swap (user-pays-gas mode, e.g. below the gasless
491
+ * floor). The API verifies the tx on-chain and counts it as a real solve —
492
+ * this is what makes user-paid volume rebate-eligible.
493
+ */
494
+ async confirmTransaction(txHash, chain) {
495
+ const headers = this.baseHeaders(true);
496
+ const res = await this.fetchWithHandshake(
497
+ () => this.fetch(`${this.baseUrl}/api/v1/confirm`, {
498
+ method: "POST",
499
+ headers,
500
+ body: JSON.stringify({ txHash, chain })
501
+ })
502
+ );
503
+ const data = await res.json().catch(() => null);
504
+ return data ?? { ok: false, error: `HTTP ${res.status}: empty response` };
505
+ }
506
+ async get(path, query = {}) {
507
+ const url = new URL(`${this.baseUrl}${path}`);
508
+ for (const [k, v] of Object.entries(query)) url.searchParams.set(k, v);
509
+ const headers = this.baseHeaders(false);
510
+ const res = await this.fetchWithHandshake(() => this.fetch(url.toString(), { headers }));
511
+ const data = await res.json().catch(() => null);
512
+ if (!data) {
513
+ return { ok: false, error: `HTTP ${res.status}: empty response` };
514
+ }
515
+ return data;
516
+ }
517
+ async post(path, body) {
518
+ const headers = this.baseHeaders(true);
519
+ const res = await this.fetchWithHandshake(
520
+ () => this.fetch(`${this.baseUrl}${path}`, {
521
+ method: "POST",
522
+ headers,
523
+ body: JSON.stringify(body)
524
+ })
525
+ );
526
+ const data = await res.json().catch(() => null);
527
+ if (!data) {
528
+ return {
529
+ ok: false,
530
+ error: `HTTP ${res.status}: empty response`,
531
+ computeMs: 0
532
+ };
533
+ }
534
+ return data;
535
+ }
536
+ };
537
+
538
+ // src/cli.ts
539
+ function parseArgs(argv) {
540
+ const positional = [];
541
+ const flags = {};
542
+ let command;
543
+ for (let i = 0; i < argv.length; i++) {
544
+ const arg = argv[i];
545
+ if (arg === "-h" || arg === "--help") {
546
+ flags.help = true;
547
+ } else if (arg === "--json") {
548
+ flags.json = true;
549
+ } else if (arg === "--execute") {
550
+ flags.execute = true;
551
+ } else if (arg === "--dry-run") {
552
+ flags.dryRun = true;
553
+ } else if (arg.startsWith("--")) {
554
+ const key = arg.slice(2);
555
+ const next = argv[i + 1];
556
+ if (next !== void 0 && !next.startsWith("-")) {
557
+ flags[key] = next;
558
+ i++;
559
+ } else {
560
+ flags[key] = true;
561
+ }
562
+ } else if (!command) {
563
+ command = arg;
564
+ } else {
565
+ positional.push(arg);
566
+ }
567
+ }
568
+ return { command, positional, flags };
569
+ }
570
+ function flag(flags, ...names) {
571
+ for (const n of names) {
572
+ const v = flags[n];
573
+ if (typeof v === "string") return v;
574
+ }
575
+ return void 0;
576
+ }
577
+ function num(flags, ...names) {
578
+ const v = flag(flags, ...names);
579
+ return v === void 0 ? void 0 : Number(v);
580
+ }
581
+ function die(message, code = 1) {
582
+ console.error(`error: ${message}`);
583
+ process.exit(code);
584
+ }
585
+ function print(data, json, formatter) {
586
+ if (json || !formatter) {
587
+ console.log(JSON.stringify(data, null, 2));
588
+ return;
589
+ }
590
+ formatter(data);
591
+ }
592
+ var HELP = `orkid \u2014 Orkid swap API CLI
593
+
594
+ Commands:
595
+ quote Get an executable swap quote
596
+ --from <sym> --to <sym> --amount <n> [--chain base]
597
+ [--from-address 0x.. --from-decimals n --to-address 0x.. --to-decimals n]
598
+ solve Sign + submit a gasless swap (needs viem + ORKID_PRIVATE_KEY)
599
+ same flags as quote, plus --user 0x.. --execute (default: dry-run)
600
+ tokens List tokens for a chain
601
+ [--chain base] [--search <q>] [--limit n]
602
+ account Show the partner account for this API key
603
+ usage List usage events for this API key's account
604
+ [--period YYYY-MM] [--event-type route|solve] [--limit n]
605
+ rebates List rebate ledger entries for this API key's account
606
+ [--period YYYY-MM]
607
+ confirm Confirm a user-submitted swap tx (counts it toward volume/rebates)
608
+ orkid confirm <txHash> [--chain base]
609
+ status API health check
610
+
611
+ Global flags:
612
+ --api-key <40-hex> Orkid API key (or ORKID_API_KEY env)
613
+ --base-url <url> API base URL (or ORKID_API_URL env, default https://orkidlabs.xyz)
614
+ --test Operator test mode (requires ORKID_OPERATOR_SECRET)
615
+ --json Raw JSON output
616
+ -h, --help This help
617
+
618
+ Environment:
619
+ ORKID_API_KEY Partner API key
620
+ ORKID_API_URL Override API base URL
621
+ ORKID_PRIVATE_KEY Wallet private key for 'solve' signing (hex, 0x-prefixed)
622
+ ORKID_OPERATOR_SECRET Operator test mode \u2014 marks usage as test (no rebate impact)
623
+ `;
624
+ async function main() {
625
+ const { command, positional, flags } = parseArgs(process.argv.slice(2));
626
+ if (!command || flags.help) {
627
+ console.log(HELP);
628
+ process.exit(command ? 0 : 1);
629
+ }
630
+ const apiKey = flag(flags, "api-key", "apiKey") || process.env.ORKID_API_KEY;
631
+ const baseUrl = flag(flags, "base-url", "baseUrl", "url") || process.env.ORKID_API_URL;
632
+ const json = Boolean(flags.json);
633
+ const testMode = Boolean(flags.test);
634
+ const operatorSecret = process.env.ORKID_OPERATOR_SECRET;
635
+ if (testMode && !operatorSecret) {
636
+ console.error("warning: --test without ORKID_OPERATOR_SECRET will record normal volume");
637
+ }
638
+ if (testMode && operatorSecret) {
639
+ console.error("test mode: usage events marked is_test \u2014 excluded from rebates");
640
+ }
641
+ const client = new OrkidClient({ apiKey, baseUrl, operatorSecret: testMode ? operatorSecret : void 0 });
642
+ switch (command) {
643
+ case "quote": {
644
+ const req = routeRequest(flags);
645
+ const res = await client.getQuote(req);
646
+ print(res, json, (r) => {
647
+ if (!r.ok) return die(r.error || "quote failed");
648
+ const q = r.quote;
649
+ console.log(`${req.amount} ${req.from} -> ${q.amountOut} ${req.to} (${q.rate})`);
650
+ console.log(`protocol: ${q.protocol}`);
651
+ console.log(`volume: $${q.volumeUsd}`);
652
+ if (r.savings) {
653
+ console.log(`savings: ${r.savings.savingsBps} bps vs MetaMask ($${r.savings.savingsUsd})`);
654
+ console.log(`orkid fee: ${r.savings.orkidBps} bps`);
655
+ }
656
+ if (q.priceImpactBps !== void 0) console.log(`impact: ${q.priceImpactBps} bps`);
657
+ console.log(`compute: ${r.computeMs}ms`);
658
+ });
659
+ if (!res.ok) process.exit(1);
660
+ break;
661
+ }
662
+ case "solve": {
663
+ const res = await cmdSolve(client, flags);
664
+ print(res, json, (r) => {
665
+ if (!r.ok) return die(r.error || "solve failed");
666
+ if (r.transaction?.txHash) console.log(`tx: ${r.transaction.txHash}`);
667
+ else console.log(`calldata ready: to=${r.transaction?.to}`);
668
+ if (r.savings) console.log(`savings: ${r.savings.savingsBps} bps ($${r.savings.savingsUsd})`);
669
+ });
670
+ if (!res.ok) process.exit(1);
671
+ break;
672
+ }
673
+ case "tokens": {
674
+ const res = await client.listTokens({
675
+ chain: flag(flags, "chain") || "base",
676
+ search: flag(flags, "search"),
677
+ limit: num(flags, "limit")
678
+ });
679
+ print(res, json, (r) => {
680
+ if (!r.ok) return die(r.error || "token lookup failed");
681
+ for (const t of r.tokens || []) {
682
+ console.log(`${t.symbol.padEnd(10)} ${t.address} (${t.decimals} decimals, ${t.chain})`);
683
+ }
684
+ if (!r.tokens?.length) console.log("no tokens found");
685
+ });
686
+ if (!res.ok) process.exit(1);
687
+ break;
688
+ }
689
+ case "account": {
690
+ const res = await client.getAccount();
691
+ print(res, json, (r) => {
692
+ if (!r.ok || !r.account) return die(r.error || "account lookup failed");
693
+ const a = r.account;
694
+ console.log(`${a.name} (${a.slug})`);
695
+ console.log(`id: ${a.id}`);
696
+ console.log(`rebate: ${a.rebate_bps} bps ${a.rebate_active ? "(active)" : "(inactive)"}`);
697
+ if (a.contact_email) console.log(`contact: ${a.contact_email}`);
698
+ });
699
+ if (!res.ok) process.exit(1);
700
+ break;
701
+ }
702
+ case "usage": {
703
+ const res = await client.getUsage({
704
+ period: flag(flags, "period", "month"),
705
+ eventType: flag(flags, "event-type", "eventType", "type"),
706
+ limit: num(flags, "limit")
707
+ });
708
+ print(res, json, (r) => {
709
+ if (r.error) return die(r.error);
710
+ for (const e of r.events || []) {
711
+ const vol = e.volume_usd != null ? `$${e.volume_usd}` : "-";
712
+ const dry = e.is_dry_run ? " [dry-run]" : "";
713
+ console.log(`${e.created_at} ${e.event_type.padEnd(6)} ${String(e.token_in || "-").padEnd(6)}->${String(e.token_out || "-").padEnd(6)} ${vol}${dry}`);
714
+ }
715
+ console.log(`---
716
+ total volume: $${r.total_volume_usd ?? 0} total savings: $${r.total_savings_usd ?? 0}`);
717
+ });
718
+ if (res.error) process.exit(1);
719
+ break;
720
+ }
721
+ case "rebates": {
722
+ const res = await client.getRebates({ period: flag(flags, "period", "month") });
723
+ print(res, json, (r) => {
724
+ if (r.error) return die(r.error);
725
+ for (const b of r.rebates || []) {
726
+ const month = String(b.period_start).slice(0, 7);
727
+ console.log(`${month} volume=$${b.volume_usd} rate=${b.rebate_bps}bps rebate=$${b.rebate_usd} [${b.status}]`);
728
+ }
729
+ if (!r.rebates?.length) console.log("no rebates accrued yet");
730
+ });
731
+ if (res.error) process.exit(1);
732
+ break;
733
+ }
734
+ case "confirm": {
735
+ const txHash = flag(flags, "tx-hash", "txHash", "tx") || positional[0];
736
+ if (!txHash) die("confirm requires a tx hash: orkid confirm 0x...");
737
+ const res = await client.confirmTransaction(txHash, flag(flags, "chain"));
738
+ print(res, json, (r) => {
739
+ if (r.confirmed) {
740
+ console.log(`confirmed \u2014 event ${r.eventId} now counts toward volume/rebates`);
741
+ } else {
742
+ die(r.error || "not confirmed");
743
+ }
744
+ });
745
+ if (!res.confirmed) process.exit(1);
746
+ break;
747
+ }
748
+ case "status": {
749
+ const res = await client.getStatus();
750
+ print(res, json, (r) => console.log(r.ok === false ? "API unreachable" : JSON.stringify(r)));
751
+ break;
752
+ }
753
+ default:
754
+ console.error(`unknown command: ${command}
755
+ `);
756
+ console.log(HELP);
757
+ process.exit(1);
758
+ }
759
+ }
760
+ function routeRequest(flags) {
761
+ const from = flag(flags, "from");
762
+ const to = flag(flags, "to");
763
+ const amount = flag(flags, "amount");
764
+ if (!from || !to || !amount) die("quote requires --from, --to, and --amount");
765
+ return {
766
+ from,
767
+ to,
768
+ amount,
769
+ chain: flag(flags, "chain") || "base",
770
+ fromAddress: flag(flags, "from-address", "fromAddress"),
771
+ fromDecimals: num(flags, "from-decimals", "fromDecimals"),
772
+ toAddress: flag(flags, "to-address", "toAddress"),
773
+ toDecimals: num(flags, "to-decimals", "toDecimals")
774
+ };
775
+ }
776
+ async function cmdSolve(client, flags) {
777
+ const req = routeRequest(flags);
778
+ if (!req.fromAddress || req.fromDecimals === void 0) {
779
+ die("solve requires --from-address and --from-decimals (token-in contract + decimals)");
780
+ }
781
+ const privateKey = flag(flags, "private-key", "privateKey") || process.env.ORKID_PRIVATE_KEY;
782
+ if (!privateKey) die("solve requires --private-key or ORKID_PRIVATE_KEY");
783
+ let viem, accounts, chainsMod;
784
+ try {
785
+ viem = await import("viem");
786
+ accounts = await import("viem/accounts");
787
+ chainsMod = await import("viem/chains");
788
+ } catch {
789
+ die("solve requires viem \u2014 run: npm install viem");
790
+ }
791
+ const { OrkidViemPermitSigner: OrkidViemPermitSigner2 } = await Promise.resolve().then(() => (init_viem(), viem_exports));
792
+ const { chainConfigFromName: chainConfigFromName2 } = await Promise.resolve().then(() => (init_chains(), chains_exports));
793
+ const chainConfig = chainConfigFromName2(req.chain || "base");
794
+ const account = accounts.privateKeyToAccount(privateKey);
795
+ const viemChain = Object.values(chainsMod).find((c) => c.id === chainConfig.id);
796
+ if (!viemChain) die(`no viem chain config for chain id ${chainConfig.id}`);
797
+ const walletClient = viem.createWalletClient({
798
+ account,
799
+ chain: viemChain,
800
+ transport: viem.http(chainConfig.rpcUrl)
801
+ });
802
+ const publicClient = viem.createPublicClient({
803
+ chain: viemChain,
804
+ transport: viem.http(chainConfig.rpcUrl)
805
+ });
806
+ const user = flag(flags, "user") || account.address;
807
+ const signer = new OrkidViemPermitSigner2(walletClient, publicClient);
808
+ const { permit, signature } = await signer.signSwap({
809
+ user,
810
+ fromToken: req.fromAddress,
811
+ fromDecimals: req.fromDecimals,
812
+ amount: req.amount,
813
+ chain: req.chain || "base"
814
+ });
815
+ const dryRun = !flags.execute;
816
+ if (!dryRun) {
817
+ console.error("submitting live swap...");
818
+ }
819
+ const res = await client.solve({
820
+ ...req,
821
+ user,
822
+ permit,
823
+ signature,
824
+ dryRun,
825
+ slippageBps: num(flags, "slippage-bps", "slippageBps")
826
+ });
827
+ if (flags.execute && !res.ok && res.gaslessEligible === false) {
828
+ console.error("below gasless minimum \u2014 submitting user-paid transaction...");
829
+ const encoded = await client.solve({
830
+ ...req,
831
+ user,
832
+ permit,
833
+ signature,
834
+ dryRun: true,
835
+ slippageBps: num(flags, "slippage-bps", "slippageBps")
836
+ });
837
+ if (!encoded.ok || !encoded.transaction?.data) return encoded;
838
+ const txHash = await walletClient.sendTransaction({
839
+ to: encoded.transaction.to,
840
+ data: encoded.transaction.data,
841
+ value: BigInt(encoded.transaction.value || "0")
842
+ });
843
+ console.error(`user-paid tx submitted: ${txHash} \u2014 waiting for confirmation...`);
844
+ await publicClient.waitForTransactionReceipt({ hash: txHash });
845
+ const confirm = await client.confirmTransaction(txHash, req.chain || "base");
846
+ if (!confirm.confirmed) {
847
+ console.error(`warning: swap settled on-chain but not recorded (${confirm.error || "confirm failed"})`);
848
+ }
849
+ return {
850
+ ...encoded,
851
+ transaction: { ...encoded.transaction, txHash }
852
+ };
853
+ }
854
+ return res;
855
+ }
856
+ main().catch((e) => die(e instanceof Error ? e.message : String(e)));