@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/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)));
|