@continuumdao/ctm-mpc-defi 0.1.3

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.
Files changed (65) hide show
  1. package/LICENSE +18 -0
  2. package/README.md +111 -0
  3. package/dist/agent/catalog.cjs +484 -0
  4. package/dist/agent/catalog.cjs.map +1 -0
  5. package/dist/agent/catalog.d.cts +117 -0
  6. package/dist/agent/catalog.d.ts +117 -0
  7. package/dist/agent/catalog.js +474 -0
  8. package/dist/agent/catalog.js.map +1 -0
  9. package/dist/chains/evm/index.cjs +474 -0
  10. package/dist/chains/evm/index.cjs.map +1 -0
  11. package/dist/chains/evm/index.d.cts +62 -0
  12. package/dist/chains/evm/index.d.ts +62 -0
  13. package/dist/chains/evm/index.js +459 -0
  14. package/dist/chains/evm/index.js.map +1 -0
  15. package/dist/chains/near/index.cjs +25 -0
  16. package/dist/chains/near/index.cjs.map +1 -0
  17. package/dist/chains/near/index.d.cts +37 -0
  18. package/dist/chains/near/index.d.ts +37 -0
  19. package/dist/chains/near/index.js +20 -0
  20. package/dist/chains/near/index.js.map +1 -0
  21. package/dist/chains/solana/index.cjs +25 -0
  22. package/dist/chains/solana/index.cjs.map +1 -0
  23. package/dist/chains/solana/index.d.cts +40 -0
  24. package/dist/chains/solana/index.d.ts +40 -0
  25. package/dist/chains/solana/index.js +20 -0
  26. package/dist/chains/solana/index.js.map +1 -0
  27. package/dist/core/index.cjs +128 -0
  28. package/dist/core/index.cjs.map +1 -0
  29. package/dist/core/index.d.cts +10 -0
  30. package/dist/core/index.d.ts +10 -0
  31. package/dist/core/index.js +116 -0
  32. package/dist/core/index.js.map +1 -0
  33. package/dist/envelope-CcE5Cz_q.d.ts +35 -0
  34. package/dist/envelope-DYDPnrHZ.d.cts +35 -0
  35. package/dist/index.cjs +2481 -0
  36. package/dist/index.cjs.map +1 -0
  37. package/dist/index.d.cts +15 -0
  38. package/dist/index.d.ts +15 -0
  39. package/dist/index.js +2446 -0
  40. package/dist/index.js.map +1 -0
  41. package/dist/keygen-CfNp8yKJ.d.cts +9 -0
  42. package/dist/keygen-DsINazx8.d.ts +9 -0
  43. package/dist/nodeRead-BnmSaMGO.d.cts +8 -0
  44. package/dist/nodeRead-BnmSaMGO.d.ts +8 -0
  45. package/dist/protocols/evm/curve-dao/index.cjs +869 -0
  46. package/dist/protocols/evm/curve-dao/index.cjs.map +1 -0
  47. package/dist/protocols/evm/curve-dao/index.d.cts +147 -0
  48. package/dist/protocols/evm/curve-dao/index.d.ts +147 -0
  49. package/dist/protocols/evm/curve-dao/index.js +846 -0
  50. package/dist/protocols/evm/curve-dao/index.js.map +1 -0
  51. package/dist/protocols/evm/uniswap-v4/index.cjs +1700 -0
  52. package/dist/protocols/evm/uniswap-v4/index.cjs.map +1 -0
  53. package/dist/protocols/evm/uniswap-v4/index.d.cts +323 -0
  54. package/dist/protocols/evm/uniswap-v4/index.d.ts +323 -0
  55. package/dist/protocols/evm/uniswap-v4/index.js +1659 -0
  56. package/dist/protocols/evm/uniswap-v4/index.js.map +1 -0
  57. package/dist/registry-BwZoE668.d.cts +8 -0
  58. package/dist/registry-oMKlO_5z.d.ts +8 -0
  59. package/dist/txParams-BC7ogvdR.d.cts +19 -0
  60. package/dist/txParams-BC7ogvdR.d.ts +19 -0
  61. package/dist/types-5u863Fd9.d.ts +34 -0
  62. package/dist/types-B8idm_gu.d.cts +34 -0
  63. package/dist/types-Ce2qNHai.d.cts +57 -0
  64. package/dist/types-Ce2qNHai.d.ts +57 -0
  65. package/package.json +94 -0
@@ -0,0 +1,1659 @@
1
+ import { getAddress, zeroAddress, formatUnits, parseUnits, encodeFunctionData, erc20Abi, defineChain, createPublicClient, http, parseGwei, serializeTransaction, keccak256, decodeFunctionData } from 'viem';
2
+
3
+ // src/core/registry.ts
4
+ var modules = [];
5
+ function registerProtocolModule(mod) {
6
+ const existing = modules.findIndex((m) => m.id === mod.id);
7
+ if (existing >= 0) {
8
+ modules[existing] = mod;
9
+ } else {
10
+ modules.push(mod);
11
+ }
12
+ }
13
+
14
+ // src/chains/evm/chainIdParse.ts
15
+ function parseEvmChainIdToNumber(chainId) {
16
+ if (chainId == null) return Number.NaN;
17
+ if (typeof chainId === "bigint") {
18
+ const n = Number(chainId);
19
+ return Number.isSafeInteger(n) && n >= 0 ? n : Number.NaN;
20
+ }
21
+ if (typeof chainId === "number") {
22
+ return Number.isInteger(chainId) && chainId >= 0 ? chainId : Number.NaN;
23
+ }
24
+ const t = String(chainId).trim();
25
+ if (!t) return Number.NaN;
26
+ const low = t.toLowerCase();
27
+ if (low.startsWith("eip155:")) {
28
+ const rest = t.slice("eip155:".length).trim();
29
+ const n = Number.parseInt(rest, 10);
30
+ return Number.isNaN(n) || n < 0 ? Number.NaN : n;
31
+ }
32
+ if (low.startsWith("0x")) {
33
+ return Number.parseInt(t, 16);
34
+ }
35
+ return Number.parseInt(t, 10);
36
+ }
37
+
38
+ // src/protocols/evm/uniswap-v4/constants.ts
39
+ var PERMIT2_ADDRESS = "0x000000000022D473030F116dDEE9F6B43aC78BA3";
40
+ var UNIVERSAL_ROUTER_SPENDER = {
41
+ 1: "0x66a9893cc07d91d95644aedd05d03f95e1dba8af",
42
+ 5: "0x3fC91A3afd70395Cd496C647d5a6CC9D4B2b7FAD",
43
+ 11155111: "0x3a9d48ab9751398bbfa63ad67599bb04e4bdf98b",
44
+ 137: "0x1095692a6237d83c6a72f3f5efedb9a670c49223",
45
+ 80001: "0x3fC91A3afd70395Cd496C647d5a6CC9D4B2b7FAD",
46
+ 10: "0x851116d9223fabed8e56c0e6b8ad0c31d98b3507",
47
+ 420: "0x3fC91A3afd70395Cd496C647d5a6CC9D4B2b7FAD",
48
+ 42161: "0xa51afafe0263b40edaef0df8781ea9aa03e381a3",
49
+ 421613: "0x3fC91A3afd70395Cd496C647d5a6CC9D4B2b7FAD",
50
+ 42220: "0xcb695bc5D3Aa22cAD1E6DF07801b061a05A0233A",
51
+ 44787: "0x3fC91A3afd70395Cd496C647d5a6CC9D4B2b7FAD",
52
+ 56: "0x1906c1d672b88cd1b9ac7593301ca990f94eae07",
53
+ 43114: "0x94b75331ae8d42c1b61065089b7d48fe14aa73b7",
54
+ 84531: "0xd0872d928672ae2ff74bdb2f5130ac12229cafaf",
55
+ 8453: "0x6fF5693b99212da76ad316178a184ab56d299b43",
56
+ 81457: "0xeabbcb3e8e415306207ef514f660a3f820025be3",
57
+ 7777777: "0x3315ef7ca28db74abadc6c44570efdf06b04b020",
58
+ 324: "0x28731BCC616B5f51dD52CF2e4dF0E78dD1136C06",
59
+ 480: "0x8ac7bee993bb44dab564ea4bc9ea67bf9eb5e743",
60
+ 1301: "0xf70536b3bcc1bd1a972dc186a2cf84cc6da6be5d",
61
+ 130: "0xef740bf23acae26f6492b10de645d6b98dc8eaf3",
62
+ 10143: "0x3ae6d8a282d67893e17aa70ebffb33ee5aa65893",
63
+ 84532: "0x492e6456d9528771018deb9e87ef7750ef184104",
64
+ 1868: "0x0e2850543f69f678257266e0907ff9a58b3f13de",
65
+ 143: "0x0d97dc33264bfc1c226207428a79b26757fb9dc3",
66
+ 59144: "0x661e93cca42afacb172121ef892830ca3b70f08d",
67
+ 4217: "0x1febb76be10aaf3a1402f04e8e835f2c382f7914",
68
+ 196: "0x5507749f2c558bb3e162c6e90c314c092e7372ff"
69
+ };
70
+ function isUniswapV4ChainSupported(chainId) {
71
+ if (chainId == null) return false;
72
+ const n = parseEvmChainIdToNumber(chainId);
73
+ if (Number.isNaN(n) || n < 0) return false;
74
+ const a = UNIVERSAL_ROUTER_SPENDER[n];
75
+ return typeof a === "string" && a.startsWith("0x");
76
+ }
77
+ function getUniswapUniversalRouterSpenderOrThrow(chainId) {
78
+ const raw = UNIVERSAL_ROUTER_SPENDER[chainId];
79
+ if (!raw || !raw.startsWith("0x")) {
80
+ throw new Error(
81
+ `No Uniswap Universal Router (spender) is configured for chainId ${chainId}. Add this chain to uniswap-v4/constants or use a chain supported by the Uniswap SDK.`
82
+ );
83
+ }
84
+ return getAddress(raw);
85
+ }
86
+ var MAX_UINT160 = (1n << 160n) - 1n;
87
+ var MAX_UINT48 = (1n << 48n) - 1n;
88
+ var UNISWAP_UNIVERSAL_ROUTER_DEFAULT_GAS_UNITS = 1500000n;
89
+ var UNISWAP_TRADE_BASE_DEFAULT = "https://trade-api.gateway.uniswap.org/v1";
90
+ var UNISWAP_UNIVERSAL_ROUTER_VERSION_DEFAULT = "2.0";
91
+ var UNISWAP_SWAP_DEFAULT_EXPIRY_MINUTES = 30;
92
+ var UNISWAP_SWAP_DEFAULT_DEADLINE_SEC_OFFSET = UNISWAP_SWAP_DEFAULT_EXPIRY_MINUTES * 60;
93
+
94
+ // src/core/nodeRead.ts
95
+ function nodeFetchWithReadAuth(url, init, auth) {
96
+ const method = (init?.method ?? "GET").toUpperCase();
97
+ const headers = new Headers(init?.headers);
98
+ if (auth.bearerOnGet && method === "GET" && auth.jwt && auth.jwt.trim()) {
99
+ headers.set("Authorization", `Bearer ${auth.jwt.trim()}`);
100
+ }
101
+ return fetch(url, { ...init, headers });
102
+ }
103
+
104
+ // src/protocols/evm/uniswap-v4/quote.ts
105
+ var DEFAULT_TRADE_BASE = "https://trade-api.gateway.uniswap.org/v1";
106
+ var UNISWAP_QUOTE_HEADERS_BASE = {
107
+ "Content-Type": "application/json",
108
+ "User-Agent": "ctm-mpc-defi uniswapTradeQuote/1.0 (TS)"
109
+ };
110
+ function parseUniswapChainId(value) {
111
+ if (typeof value === "number") {
112
+ if (!Number.isInteger(value) || value < 0) {
113
+ throw new TypeError("chainId number must be a non-negative integer");
114
+ }
115
+ return value;
116
+ }
117
+ const t = value.trim();
118
+ if (t.toLowerCase().startsWith("0x")) {
119
+ return Number.parseInt(t, 16);
120
+ }
121
+ return Number.parseInt(t, 10);
122
+ }
123
+ function trimAddr(a) {
124
+ return a.trim();
125
+ }
126
+ function isUniswapTokenInAddressNative(tokenIn) {
127
+ const t = (tokenIn ?? "").toString().trim();
128
+ if (!t) return false;
129
+ try {
130
+ return getAddress(t) === zeroAddress;
131
+ } catch {
132
+ return t.toLowerCase() === "0x0000000000000000000000000000000000000000";
133
+ }
134
+ }
135
+ function isUniswapFullQuoteResponseNativeIn(stored) {
136
+ if (!stored) return false;
137
+ const q = stored.quote;
138
+ if (!q || typeof q !== "object" || Array.isArray(q)) return false;
139
+ const input = q.input;
140
+ if (!input) return false;
141
+ const raw = (input.address ?? input.token ?? "").toString().trim();
142
+ return isUniswapTokenInAddressNative(raw);
143
+ }
144
+ async function fetchEthereumAddressForKeyGen(managementNodeUrl, keyGenId, readAuth = { bearerOnGet: true, jwt: null }, init) {
145
+ const base = managementNodeUrl.trim().replace(/\/$/, "");
146
+ if (!base) {
147
+ throw new Error("managementNodeUrl is required to resolve keyGen");
148
+ }
149
+ const id = (keyGenId || "").trim();
150
+ if (!id) {
151
+ throw new Error("keyGen is required");
152
+ }
153
+ const url = `${base}/getKeyGenResultById?id=${encodeURIComponent(id)}`;
154
+ const res = await nodeFetchWithReadAuth(url, { ...init, method: "GET", cache: "no-store" }, readAuth);
155
+ if (!res.ok) {
156
+ const t = await res.text().catch(() => "");
157
+ throw new Error(
158
+ `getKeyGenResultById HTTP ${res.status}: ${t || res.statusText}`.trim()
159
+ );
160
+ }
161
+ const raw = await res.json();
162
+ const d = raw.Data ?? raw.data;
163
+ const eth = d?.ethereumaddress;
164
+ if (typeof eth !== "string" || !eth.trim()) {
165
+ throw new Error("getKeyGenResultById: ethereumaddress missing for this keyGen");
166
+ }
167
+ return eth.trim();
168
+ }
169
+ function resolveInputChainId(p) {
170
+ if (p.chainId !== void 0 && p.chainId !== null && String(p.chainId).trim() !== "") {
171
+ return p.chainId;
172
+ }
173
+ if (p.tokenInChainId !== void 0 && p.tokenInChainId !== null && String(p.tokenInChainId).trim() !== "") {
174
+ return p.tokenInChainId;
175
+ }
176
+ if (p.tokenOutChainId !== void 0 && p.tokenOutChainId !== null && String(p.tokenOutChainId).trim() !== "") {
177
+ return p.tokenOutChainId;
178
+ }
179
+ throw new Error("Set chainId, tokenInChainId, or tokenOutChainId (same-chain) for the input side");
180
+ }
181
+ function buildUniswapQuoteRequestBody(args) {
182
+ const cIn = parseUniswapChainId(resolveInputChainId(args));
183
+ const cOut = args.tokenOutChainId !== void 0 && args.tokenOutChainId !== null && String(args.tokenOutChainId).trim() !== "" ? parseUniswapChainId(args.tokenOutChainId) : cIn;
184
+ const body = {
185
+ type: args.type,
186
+ amount: String(args.amount).trim(),
187
+ tokenInChainId: cIn,
188
+ tokenOutChainId: cOut,
189
+ tokenIn: trimAddr(args.tokenIn),
190
+ tokenOut: trimAddr(args.tokenOut),
191
+ swapper: trimAddr(args.swapper)
192
+ };
193
+ if (args.slippage !== void 0 && args.slippage !== null && String(args.slippage).trim() !== "") {
194
+ const s = typeof args.slippage === "number" ? args.slippage : Number.parseFloat(String(args.slippage).trim());
195
+ if (Number.isNaN(s)) {
196
+ throw new TypeError("slippage must be a number or a numeric string");
197
+ }
198
+ body.slippageTolerance = s;
199
+ } else {
200
+ body.autoSlippage = "DEFAULT";
201
+ }
202
+ return body;
203
+ }
204
+ function errorMessageFromUniswapJsonBody(value) {
205
+ if (value == null) return null;
206
+ if (typeof value === "string") {
207
+ const t = value.trim();
208
+ return t || null;
209
+ }
210
+ if (typeof value !== "object") return null;
211
+ if (Array.isArray(value)) {
212
+ if (value.length === 0) return null;
213
+ return errorMessageFromUniswapJsonBody(value[0]);
214
+ }
215
+ const o = value;
216
+ if (typeof o.detail === "string" && o.detail.trim()) {
217
+ const base = o.detail.trim();
218
+ const code = typeof o.errorCode === "string" && o.errorCode.trim() ? o.errorCode.trim() : "";
219
+ const req = typeof o.requestId === "string" && o.requestId.trim() ? o.requestId.trim() : "";
220
+ if (code) {
221
+ return req ? `${base} (${code}) [${req}]` : `${base} (${code})`;
222
+ }
223
+ return req ? `${base} [${req}]` : base;
224
+ }
225
+ if (typeof o.errorCode === "string" && o.errorCode.trim()) {
226
+ return o.errorCode.trim();
227
+ }
228
+ for (const k of ["message", "detail", "description", "title", "reason"]) {
229
+ const v = o[k];
230
+ if (typeof v === "string" && v.trim()) return v.trim();
231
+ }
232
+ if (typeof o.error === "string" && o.error.trim()) return o.error.trim();
233
+ if (o.error != null && typeof o.error === "object") {
234
+ const inner = errorMessageFromUniswapJsonBody(o.error);
235
+ if (inner) return inner;
236
+ }
237
+ if (Array.isArray(o.errors) && o.errors.length > 0) {
238
+ const inner = errorMessageFromUniswapJsonBody(o.errors[0]);
239
+ if (inner) return inner;
240
+ }
241
+ if (typeof o.code === "string" && o.code.trim()) return o.code.trim();
242
+ return null;
243
+ }
244
+ function bodyLooksLikeHtml(t) {
245
+ const s = t.slice(0, 256).trimStart();
246
+ return s.startsWith("<!") || s.toLowerCase().startsWith("<html");
247
+ }
248
+ function titleFromHtml(t) {
249
+ const m = t.match(/<title[^>]*>([^<]+)<\/title>/i);
250
+ if (!m?.[1]) return null;
251
+ const s = m[1].replace(/\s+/g, " ").trim();
252
+ return s || null;
253
+ }
254
+ function messageFromUniswapHttpResponseBody(text, status, statusText = "") {
255
+ const raw = (text ?? "").trim();
256
+ const st = (statusText ?? "").trim();
257
+ if (!raw) {
258
+ if (status > 0) {
259
+ return st ? `HTTP ${status} (${st})` : `HTTP ${status}`;
260
+ }
261
+ return "Empty response";
262
+ }
263
+ if (bodyLooksLikeHtml(raw)) {
264
+ const title = titleFromHtml(raw);
265
+ if (title) {
266
+ return status > 0 ? `HTTP ${status}: ${title}` : title;
267
+ }
268
+ return status > 0 ? "HTTP " + String(status) + ": response was not JSON (HTML or error page). Check API key, token pair, and network." : "Response was not JSON (HTML or error page).";
269
+ }
270
+ const forParse = raw.replace(/^\uFEFF/, "");
271
+ try {
272
+ const j = JSON.parse(forParse);
273
+ if (j && typeof j === "object") {
274
+ const msg = errorMessageFromUniswapJsonBody(j);
275
+ if (msg) {
276
+ if (status >= 400) {
277
+ return st ? `${msg} (HTTP ${status} ${st})` : `${msg} (HTTP ${status})`;
278
+ }
279
+ return msg;
280
+ }
281
+ }
282
+ } catch {
283
+ }
284
+ const oneLine = raw.replace(/\s+/g, " ").trim();
285
+ const short = oneLine.length > 500 ? `${oneLine.slice(0, 500)}\u2026` : oneLine;
286
+ if (status > 0) {
287
+ return st && !short ? `HTTP ${status} (${st})` : st ? `HTTP ${status}: ${short} (${st})` : `HTTP ${status}: ${short}`;
288
+ }
289
+ return short;
290
+ }
291
+ async function uniswapTradeQuote(args) {
292
+ const apiKey = (args.uniswapApiKey || "").trim();
293
+ if (!apiKey) {
294
+ throw new Error("uniswapApiKey (x-api-key) is required");
295
+ }
296
+ const keyGen = (args.keyGen || "").trim();
297
+ let swapper;
298
+ if ((args.swapper || "").trim()) {
299
+ swapper = trimAddr(args.swapper);
300
+ } else {
301
+ if (!keyGen) {
302
+ throw new Error("keyGen is required when swapper is not provided");
303
+ }
304
+ const mpc = (args.managementNodeUrl || "").trim();
305
+ if (!mpc) {
306
+ throw new Error("managementNodeUrl is required when swapper is not provided (to resolve keyGen)");
307
+ }
308
+ swapper = await fetchEthereumAddressForKeyGen(
309
+ mpc,
310
+ keyGen,
311
+ args.nodeReadAuth ?? { bearerOnGet: true, jwt: null }
312
+ );
313
+ }
314
+ const base = args.baseUrl && args.baseUrl.trim() ? args.baseUrl.trim().replace(/\/$/, "") : DEFAULT_TRADE_BASE;
315
+ const quoteUrl = `${base}/quote`;
316
+ const urv = args.universalRouterVersion && args.universalRouterVersion.trim() || "2.0";
317
+ const body = buildUniswapQuoteRequestBody({ ...args, swapper });
318
+ const nativeIn = isUniswapTokenInAddressNative(args.tokenIn);
319
+ const headers = {
320
+ ...UNISWAP_QUOTE_HEADERS_BASE,
321
+ "x-api-key": apiKey,
322
+ "x-universal-router-version": urv,
323
+ "x-permit2-disabled": args.permit2Disabled === true ? "true" : "false",
324
+ /** Native (0x0) as token in: Trade API needs this for classic routes and payable /swap. */
325
+ "x-erc20eth-enabled": nativeIn ? "true" : "false"
326
+ };
327
+ const fetchFn = args.fetchImpl ?? globalThis.fetch;
328
+ const res = await fetchFn(quoteUrl, {
329
+ method: "POST",
330
+ headers,
331
+ body: JSON.stringify(body),
332
+ signal: args.signal
333
+ });
334
+ const text = await res.text();
335
+ if (!res.ok) {
336
+ throw new Error(messageFromUniswapHttpResponseBody(text, res.status, res.statusText));
337
+ }
338
+ const forParse = text.replace(/^\uFEFF/, "");
339
+ try {
340
+ return JSON.parse(forParse);
341
+ } catch {
342
+ throw new Error(
343
+ `Trade API: ${messageFromUniswapHttpResponseBody(text, res.status, res.statusText)}`
344
+ );
345
+ }
346
+ }
347
+ function uniswapQuoteToJsonQuoteOneLine(quote) {
348
+ return JSON.stringify(quote);
349
+ }
350
+ function parseUniswapQuoteClassicInOut(res) {
351
+ const inner = res.quote;
352
+ if (!inner || typeof inner !== "object" || Array.isArray(inner)) return null;
353
+ const q = inner;
354
+ const input = q.input;
355
+ const output = q.output;
356
+ const inAm = input?.amount;
357
+ const outAm = output?.amount;
358
+ if (typeof inAm !== "string" || !inAm || typeof outAm !== "string" || !outAm) return null;
359
+ try {
360
+ return { inputWei: BigInt(inAm), outputWei: BigInt(outAm) };
361
+ } catch {
362
+ return null;
363
+ }
364
+ }
365
+ function parseUniswapQuoteSlippageInfo(res) {
366
+ const inner = res.quote;
367
+ if (!inner || typeof inner !== "object" || Array.isArray(inner)) {
368
+ return { slippageTolerancePercent: null, priceImpactPercent: null };
369
+ }
370
+ const q = inner;
371
+ const sRaw = q.slippage ?? q.slippageTolerance;
372
+ let slippageTolerancePercent = null;
373
+ if (typeof sRaw === "number" && Number.isFinite(sRaw)) {
374
+ slippageTolerancePercent = sRaw;
375
+ } else if (typeof sRaw === "string" && sRaw.trim()) {
376
+ const p = Number.parseFloat(sRaw.trim().replace(/,/g, ""));
377
+ if (Number.isFinite(p)) slippageTolerancePercent = p;
378
+ }
379
+ const piRaw = q.priceImpact;
380
+ let priceImpactPercent = null;
381
+ if (typeof piRaw === "number" && Number.isFinite(piRaw)) {
382
+ priceImpactPercent = piRaw;
383
+ } else if (typeof piRaw === "string" && piRaw.trim()) {
384
+ const p = Number.parseFloat(piRaw.trim().replace(/,/g, ""));
385
+ if (Number.isFinite(p)) priceImpactPercent = p;
386
+ }
387
+ return { slippageTolerancePercent, priceImpactPercent };
388
+ }
389
+ function formatUniswapPercentForUi(n) {
390
+ if (!Number.isFinite(n)) return "\u2014";
391
+ const t = n.toFixed(4).replace(/\.?0+$/, "");
392
+ return t || "0";
393
+ }
394
+ function formatUniswapAmountFieldFromWei(wei, decimals) {
395
+ const s = formatUnits(wei, decimals);
396
+ if (!s.includes(".")) return s;
397
+ const t = s.replace(/0+$/, "");
398
+ return t.endsWith(".") ? t.slice(0, -1) : t;
399
+ }
400
+ function buildUniswapV4PurposePrefill(args) {
401
+ const symIn = (args.tokenInSymbol || "").trim() || "token in";
402
+ const symOut = (args.tokenOutSymbol || "").trim() || "token out";
403
+ const rawS = String(args.slippageInput || "0.5").trim().replace(/,/g, "");
404
+ const parsedSlip = Number.parseFloat(rawS);
405
+ const slip = Number.isFinite(parsedSlip) ? parsedSlip : 0.5;
406
+ const slipText = (Math.round(slip * 100) / 100).toString();
407
+ const p = args.quote != null ? parseUniswapQuoteClassicInOut(args.quote) : null;
408
+ let inPart = "\u2026";
409
+ let outPart = "\u2026";
410
+ if (p) {
411
+ inPart = `${formatUniswapAmountFieldFromWei(p.inputWei, args.tokenInDecimals)} ${symIn}`.replace(/\s+/g, " ").trim();
412
+ outPart = `${formatUniswapAmountFieldFromWei(p.outputWei, args.tokenOutDecimals)} ${symOut}`.replace(/\s+/g, " ").trim();
413
+ } else if (args.tradeType === "EXACT_INPUT" && args.amountInput.trim()) {
414
+ try {
415
+ const w = parseUnits(args.amountInput.trim(), args.tokenInDecimals);
416
+ inPart = `${formatUniswapAmountFieldFromWei(w, args.tokenInDecimals)} ${symIn}`.replace(/\s+/g, " ").trim();
417
+ outPart = `\u2026 ${symOut}`;
418
+ } catch {
419
+ }
420
+ }
421
+ const cname = (args.chainName || "").trim() || "chain";
422
+ return `Swap ${inPart} for ${outPart} with a slippage of ${slipText}% on ${cname} (chainId ${args.chainId})`;
423
+ }
424
+ function formatUniswapQuoteForDisplay(res, args) {
425
+ const { tradeType, tokenInDecimals, tokenOutDecimals, tokenInSymbol, tokenOutSymbol } = args;
426
+ const rawJson = JSON.stringify(res, null, 2);
427
+ const routing = typeof res.routing === "string" ? res.routing : void 0;
428
+ const requestId = typeof res.requestId === "string" ? res.requestId : void 0;
429
+ const inner = res.quote;
430
+ if (inner && typeof inner === "object" && !Array.isArray(inner)) {
431
+ const q = inner;
432
+ const input = q.input;
433
+ const output = q.output;
434
+ const inAm = input?.amount;
435
+ const outAm = output?.amount;
436
+ if (typeof inAm === "string" && inAm && typeof outAm === "string" && outAm) {
437
+ try {
438
+ const inH = formatUnits(BigInt(inAm), tokenInDecimals);
439
+ const outH = formatUnits(BigInt(outAm), tokenOutDecimals);
440
+ const title = tradeType === "EXACT_INPUT" ? `\u2248 ${outH} ${tokenOutSymbol} for ${inH} ${tokenInSymbol} (est.)` : `\u2248 ${inH} ${tokenInSymbol} for ${outH} ${tokenOutSymbol} (est.)`;
441
+ const lines2 = [
442
+ `Input: ${inH} ${tokenInSymbol} (${inAm} wei)`,
443
+ `Output: ${outH} ${tokenOutSymbol} (${outAm} wei)`
444
+ ];
445
+ if (routing) lines2.push(`Route: ${routing}`);
446
+ if (requestId) lines2.push(`Request: ${requestId.slice(0, 24)}\u2026`);
447
+ return { title, lines: lines2, rawJson };
448
+ } catch {
449
+ }
450
+ }
451
+ }
452
+ const lines = ["(Could not parse classic input/output; see raw JSON below.)"];
453
+ if (routing) lines.push(`Route: ${routing}`);
454
+ if (requestId) lines.push(`Request: ${requestId}`);
455
+ return { title: "Quote received", lines, rawJson };
456
+ }
457
+
458
+ // src/core/keygen.ts
459
+ function firstClientIdFromKeyGen(data) {
460
+ const map = data?.ClientKeys;
461
+ if (!map || typeof map !== "object") return null;
462
+ for (const v of Object.values(map)) {
463
+ if (typeof v === "string" && v.trim()) return v.trim();
464
+ }
465
+ return null;
466
+ }
467
+
468
+ // src/chains/evm/txParams.ts
469
+ function gasLimitFromEstimateAndChainConfig(estimatedGas, chainGasLimit) {
470
+ if (chainGasLimit == null || !Number.isFinite(chainGasLimit) || chainGasLimit <= 0) {
471
+ return estimatedGas;
472
+ }
473
+ const cfg = BigInt(Math.floor(chainGasLimit));
474
+ return cfg > estimatedGas ? cfg : estimatedGas;
475
+ }
476
+ function routerSwapGasLimitFromEstimate(estimatedGas, chainGasLimit) {
477
+ if (chainGasLimit != null && Number.isFinite(chainGasLimit) && chainGasLimit > 0) {
478
+ return gasLimitFromEstimateAndChainConfig(estimatedGas, chainGasLimit);
479
+ }
480
+ return (estimatedGas * 12n + 9n) / 10n;
481
+ }
482
+ async function fetchChainFeeParams(rpcUrl, chainId) {
483
+ const url = rpcUrl.trim();
484
+ if (!url) return { isEip1559: false };
485
+ const chainIdNum = typeof chainId === "string" ? parseInt(chainId, 10) : chainId;
486
+ if (Number.isNaN(chainIdNum)) return { isEip1559: false };
487
+ const chain = defineChain({
488
+ id: chainIdNum,
489
+ name: "Discovery",
490
+ nativeCurrency: { decimals: 18, name: "Ether", symbol: "ETH" },
491
+ rpcUrls: { default: { http: [url] } }
492
+ });
493
+ const publicClient = createPublicClient({
494
+ chain,
495
+ transport: http(url)
496
+ });
497
+ const getGasPriceGwei = async () => {
498
+ const gasPriceWei = await publicClient.getGasPrice();
499
+ return parseFloat(formatUnits(gasPriceWei, 9));
500
+ };
501
+ try {
502
+ const block = await publicClient.getBlock({ blockTag: "latest" });
503
+ const baseFeePerGas = block?.baseFeePerGas;
504
+ if (baseFeePerGas == null || baseFeePerGas === void 0) {
505
+ const gasPriceGwei2 = await getGasPriceGwei();
506
+ return { isEip1559: false, gasPriceGwei: gasPriceGwei2 };
507
+ }
508
+ const baseFeeGwei = parseFloat(formatUnits(baseFeePerGas, 9));
509
+ let priorityFeeGwei;
510
+ try {
511
+ const priorityWei = await publicClient.estimateMaxPriorityFeePerGas();
512
+ priorityFeeGwei = parseFloat(formatUnits(priorityWei, 9));
513
+ } catch {
514
+ }
515
+ const gasPriceGwei = await getGasPriceGwei();
516
+ return {
517
+ isEip1559: true,
518
+ baseFeeGwei,
519
+ priorityFeeGwei,
520
+ gasPriceGwei
521
+ };
522
+ } catch {
523
+ try {
524
+ const gasPriceWei = await publicClient.getGasPrice();
525
+ const gasPriceGwei = parseFloat(formatUnits(gasPriceWei, 9));
526
+ return { isEip1559: false, gasPriceGwei };
527
+ } catch {
528
+ return { isEip1559: false };
529
+ }
530
+ }
531
+ }
532
+ function finalizeEip1559Fees(maxFeePerGas, maxPriorityFeePerGas, floor, baseWei) {
533
+ let maxP = maxPriorityFeePerGas;
534
+ let maxF = maxFeePerGas;
535
+ if (baseWei > 0n && maxF < baseWei + maxP) {
536
+ maxF = baseWei + maxP + parseGwei("0.001");
537
+ }
538
+ if (maxF < maxP) {
539
+ maxF = baseWei > 0n ? baseWei + maxP + parseGwei("0.001") : maxP * 2n;
540
+ }
541
+ return { maxFeePerGas: maxF, maxPriorityFeePerGas: maxP };
542
+ }
543
+ function alignEip1559FeesWithLatestBase(maxFeePerGas, maxPriorityFeePerGas, latestBlockBaseFeeWei) {
544
+ return finalizeEip1559Fees(maxFeePerGas, maxPriorityFeePerGas, null, latestBlockBaseFeeWei);
545
+ }
546
+ function gweiToDecimalString(n) {
547
+ if (!Number.isFinite(n)) return "0";
548
+ if (n === 0) return "0";
549
+ const s = String(n);
550
+ if (s.indexOf("e") !== -1 || s.indexOf("E") !== -1) return n.toFixed(9).replace(/\.?0+$/, "") || "0";
551
+ return s;
552
+ }
553
+
554
+ // src/protocols/evm/uniswap-v4/swapMultisign.ts
555
+ function parseOptionalGasLimitString(raw) {
556
+ if (raw == null) return null;
557
+ const s = String(raw).trim();
558
+ if (!s) return null;
559
+ try {
560
+ if (/^0x[0-9a-fA-F]+$/.test(s)) return BigInt(s);
561
+ return BigInt(s);
562
+ } catch {
563
+ return null;
564
+ }
565
+ }
566
+ function parseExtraJsonObject(detail) {
567
+ if (!detail) return null;
568
+ const raw = detail.ExtraJSON ?? detail.extraJSON;
569
+ if (raw == null) return null;
570
+ if (typeof raw === "object" && !Array.isArray(raw)) return raw;
571
+ if (typeof raw === "string" && raw.trim()) {
572
+ try {
573
+ const p = JSON.parse(raw);
574
+ if (p && typeof p === "object" && !Array.isArray(p)) return p;
575
+ } catch {
576
+ return null;
577
+ }
578
+ }
579
+ return null;
580
+ }
581
+ function isUniswapV4SwapEvmSignRequest(detail, batchIndex) {
582
+ const ex = parseExtraJsonObject(detail);
583
+ if (batchIndex != null && batchIndex >= 0 && ex) {
584
+ const bm = ex.batchMeta;
585
+ if (Array.isArray(bm) && bm[batchIndex] && typeof bm[batchIndex] === "object" && !Array.isArray(bm[batchIndex])) {
586
+ const evm0 = bm[batchIndex].evm;
587
+ if (evm0 && typeof evm0 === "object" && !Array.isArray(evm0)) {
588
+ if (String(evm0.type ?? "") === "uniswap_v4_swap_tx") return true;
589
+ }
590
+ }
591
+ }
592
+ const evm = ex?.evm;
593
+ if (!evm || typeof evm !== "object" || Array.isArray(evm)) return false;
594
+ return String(evm.type ?? "") === "uniswap_v4_swap_tx";
595
+ }
596
+ function resolveRouterSwapGasUnitsFromSignRequest(detail, batchIndex) {
597
+ if (!detail) return null;
598
+ const propBatch = detail.proposal_tx_params ?? detail.proposalTxParams ?? detail.ProposalTxParams;
599
+ const index = batchIndex != null && batchIndex >= 0 ? batchIndex : 0;
600
+ if (Array.isArray(propBatch) && propBatch.length > index) {
601
+ const row = propBatch[index];
602
+ if (row && typeof row === "object" && !Array.isArray(row)) {
603
+ const r = row;
604
+ const gl = parseOptionalGasLimitString(
605
+ r.gas_limit ?? r.gasLimit ?? r.GasLimit
606
+ );
607
+ if (gl != null && gl > 0n) return gl;
608
+ }
609
+ }
610
+ if (batchIndex == null || batchIndex === 0) {
611
+ const tp = detail.txParams ?? detail.TxParams;
612
+ if (tp && typeof tp === "object") {
613
+ const gl = parseOptionalGasLimitString(
614
+ tp.gasLimit ?? tp.GasLimit ?? tp.txGasLimit
615
+ );
616
+ if (gl != null && gl > 0n) return gl;
617
+ }
618
+ }
619
+ const ex = parseExtraJsonObject(detail);
620
+ const u4 = ex?.uniswapV4;
621
+ if (u4 && typeof u4 === "object") {
622
+ const uc = u4.uniswapCreateSwap;
623
+ const gb = uc?.gasBuild;
624
+ const base = parseOptionalGasLimitString(gb?.baseGasUnits);
625
+ if (base != null && base > 0n) return base;
626
+ const snap = u4.fullQuoteFromPermitSnapshot;
627
+ const quote = snap?.quote;
628
+ const gue = parseOptionalGasLimitString(quote?.gasUseEstimate);
629
+ if (gue != null && gue > 0n) return gue;
630
+ }
631
+ return null;
632
+ }
633
+ var DEFAULT_UNIVERSAL_ROUTER_GAS_FALLBACK = UNISWAP_UNIVERSAL_ROUTER_DEFAULT_GAS_UNITS;
634
+ function swapTransactionDeadlineUnixFromExpiryMinutes(expiryMinutes, nowSec = Math.floor(Date.now() / 1e3)) {
635
+ const m = Number.isFinite(expiryMinutes) && expiryMinutes > 0 ? expiryMinutes : UNISWAP_SWAP_DEFAULT_EXPIRY_MINUTES;
636
+ return nowSec + Math.floor(m * 60);
637
+ }
638
+ function swapDeadlineUnixSeconds(args) {
639
+ const now = Math.floor(Date.now() / 1e3);
640
+ if (args.swapTransactionDeadlineUnix != null && Number.isFinite(args.swapTransactionDeadlineUnix) && args.swapTransactionDeadlineUnix > now) {
641
+ return Math.floor(args.swapTransactionDeadlineUnix);
642
+ }
643
+ return swapTransactionDeadlineUnixFromExpiryMinutes(UNISWAP_SWAP_DEFAULT_EXPIRY_MINUTES, now);
644
+ }
645
+ function getClassicQuoteFromStoredUniswapResponse(stored) {
646
+ if (!stored) return null;
647
+ const q = stored.quote;
648
+ return q != null && typeof q === "object" ? q : null;
649
+ }
650
+ async function uniswapCreateSwap(args) {
651
+ const deadline = swapDeadlineUnixSeconds(args);
652
+ const useProxy = args.useServerProxy !== false && typeof globalThis !== "undefined" && typeof globalThis.window !== "undefined";
653
+ if (useProxy) {
654
+ const proxyPayload = {
655
+ uniswapApiKey: args.uniswapApiKey,
656
+ fullQuoteFromPermit: args.fullQuoteFromPermit,
657
+ universalRouterVersion: args.universalRouterVersion,
658
+ baseUrl: args.baseUrl,
659
+ swapTransactionDeadlineUnix: deadline
660
+ };
661
+ const res2 = await (args.fetchImpl ?? globalThis.fetch)("/api/uniswap/swap", {
662
+ method: "POST",
663
+ headers: { "Content-Type": "application/json" },
664
+ body: JSON.stringify(proxyPayload),
665
+ credentials: "same-origin"
666
+ });
667
+ const text2 = await res2.text();
668
+ if (!res2.ok) {
669
+ let errMsg = res2.statusText;
670
+ try {
671
+ const j = JSON.parse(text2);
672
+ if (j && typeof j.error === "string" && j.error.trim()) errMsg = j.error.trim();
673
+ } catch {
674
+ if (text2.trim()) errMsg = text2.slice(0, 500);
675
+ }
676
+ throw new Error(`Uniswap swap proxy failed: ${errMsg}`);
677
+ }
678
+ const parsed2 = JSON.parse(text2);
679
+ if (!parsed2?.swap || typeof parsed2.swap !== "object") {
680
+ throw new Error("Uniswap /swap (proxy): missing `swap` in response.");
681
+ }
682
+ return parsed2;
683
+ }
684
+ const base = (args.baseUrl ?? UNISWAP_TRADE_BASE_DEFAULT).replace(/\/$/, "");
685
+ const url = `${base}/swap`;
686
+ const classic = getClassicQuoteFromStoredUniswapResponse(args.fullQuoteFromPermit);
687
+ if (!classic) {
688
+ throw new Error("Stored Uniswap quote is missing a classic `quote` object; cannot build swap calldata.");
689
+ }
690
+ const body = {
691
+ quote: classic,
692
+ deadline
693
+ };
694
+ const urv = (args.universalRouterVersion ?? UNISWAP_UNIVERSAL_ROUTER_VERSION_DEFAULT).trim() || "2.0";
695
+ const fn = args.fetchImpl ?? globalThis.fetch;
696
+ const nativeIn = isUniswapFullQuoteResponseNativeIn(args.fullQuoteFromPermit);
697
+ const res = await fn(url, {
698
+ method: "POST",
699
+ headers: {
700
+ "Content-Type": "application/json",
701
+ "x-api-key": args.uniswapApiKey.trim(),
702
+ "x-universal-router-version": urv,
703
+ "x-erc20eth-enabled": nativeIn ? "true" : "false",
704
+ "x-permit2-disabled": "true"
705
+ },
706
+ body: JSON.stringify(body)
707
+ });
708
+ const text = await res.text();
709
+ if (!res.ok) {
710
+ throw new Error(
711
+ `Uniswap POST /swap failed: ${messageFromUniswapHttpResponseBody(text, res.status, res.statusText)}`
712
+ );
713
+ }
714
+ const parsed = JSON.parse(text);
715
+ if (!parsed?.swap || typeof parsed.swap !== "object") {
716
+ throw new Error("Uniswap /swap: missing `swap` in response.");
717
+ }
718
+ return parsed;
719
+ }
720
+ var permit2ApproveRouterAbi = [
721
+ {
722
+ name: "approve",
723
+ type: "function",
724
+ stateMutability: "nonpayable",
725
+ inputs: [
726
+ { name: "token", type: "address" },
727
+ { name: "spender", type: "address" },
728
+ { name: "amount", type: "uint160" },
729
+ { name: "expiration", type: "uint48" }
730
+ ],
731
+ outputs: []
732
+ }
733
+ ];
734
+ var uniswapDispatchExecuteAbi = [
735
+ {
736
+ name: "execute",
737
+ type: "function",
738
+ stateMutability: "payable",
739
+ inputs: [
740
+ { name: "router", type: "address" },
741
+ { name: "token", type: "address" },
742
+ { name: "amount", type: "uint256" },
743
+ { name: "commands", type: "bytes" },
744
+ { name: "inputs", type: "bytes[]" },
745
+ { name: "deadline", type: "uint256" }
746
+ ],
747
+ outputs: []
748
+ }
749
+ ];
750
+ function parseSwapTxValueWei(swap) {
751
+ const raw = swap.value ?? swap.Value;
752
+ if (raw == null || raw === "") return 0n;
753
+ if (typeof raw === "bigint") return raw > 0n ? raw : 0n;
754
+ if (typeof raw === "number" && Number.isFinite(raw)) {
755
+ try {
756
+ return BigInt(Math.max(0, Math.trunc(raw)));
757
+ } catch {
758
+ return 0n;
759
+ }
760
+ }
761
+ const s = String(raw).trim();
762
+ if (!s) return 0n;
763
+ try {
764
+ if (/^0x[0-9a-fA-F]+$/.test(s)) return BigInt(s);
765
+ return BigInt(s.replace(/^0x/i, ""));
766
+ } catch {
767
+ return 0n;
768
+ }
769
+ }
770
+ function nativeEthInPayableValueWei(args) {
771
+ const fromApi = parseSwapTxValueWei(args.swap);
772
+ if (fromApi > 0n) return fromApi;
773
+ try {
774
+ const d = decodeFunctionData({ abi: uniswapDispatchExecuteAbi, data: args.dataHex });
775
+ if (d.functionName === "execute") {
776
+ const rawAmt = d.args[2];
777
+ const amount = typeof rawAmt === "bigint" ? rawAmt : BigInt(String(rawAmt));
778
+ if (amount > 0n) return amount;
779
+ }
780
+ } catch {
781
+ }
782
+ return args.quoteInputWei > 0n ? args.quoteInputWei : 0n;
783
+ }
784
+ function applySlippagePercentToApproveWei(baseWei, slippagePercent) {
785
+ if (baseWei <= 0n) return baseWei;
786
+ const s = slippagePercent == null ? NaN : Number(slippagePercent);
787
+ if (!Number.isFinite(s) || s <= 0) return baseWei;
788
+ const bps = Math.min(1e5, Math.round(s * 100));
789
+ if (bps <= 0) return baseWei;
790
+ return (baseWei * BigInt(1e4 + bps) + 9999n) / 10000n;
791
+ }
792
+ function permit2SpenderAndApproveWeiFromSwapCalldata(dataHex, tokenIn, quoteInputWei, chainId) {
793
+ const canonical = getUniswapUniversalRouterSpenderOrThrow(chainId);
794
+ try {
795
+ const d = decodeFunctionData({ abi: uniswapDispatchExecuteAbi, data: dataHex });
796
+ if (d.functionName !== "execute") {
797
+ return { permit2Spender: canonical, approveWei: quoteInputWei, calldataAmountWei: null };
798
+ }
799
+ const router = getAddress(d.args[0]);
800
+ const token = getAddress(d.args[1]);
801
+ const rawAmt = d.args[2];
802
+ const amount = typeof rawAmt === "bigint" ? rawAmt : BigInt(String(rawAmt));
803
+ if (token.toLowerCase() !== tokenIn.toLowerCase()) {
804
+ throw new Error(
805
+ `Swap calldata token ${token} does not match token in ${tokenIn}. Refresh quote and /swap.`
806
+ );
807
+ }
808
+ const calldataAmountWei = amount > 0n ? amount : null;
809
+ let approveWei = quoteInputWei;
810
+ if (calldataAmountWei != null && calldataAmountWei > approveWei) {
811
+ approveWei = calldataAmountWei;
812
+ }
813
+ return { permit2Spender: router, approveWei, calldataAmountWei };
814
+ } catch (e) {
815
+ if (e instanceof Error && e.message.includes("does not match token")) throw e;
816
+ return { permit2Spender: canonical, approveWei: quoteInputWei, calldataAmountWei: null };
817
+ }
818
+ }
819
+ async function buildEvmMultisignBodyUniswapV4NativeInOnly(args, quoteInputWei) {
820
+ const ph = (args.keyGen.pubkeyhex ?? "").trim();
821
+ if (!ph) throw new Error("keyGen pubKey (pubkeyhex) is required");
822
+ const keyList = args.keyGen.keylist ?? [];
823
+ const clientId = firstClientIdFromKeyGen(args.keyGen);
824
+ const toRouter = getAddress(
825
+ (args.swap.to ?? "").trim().startsWith("0x") ? args.swap.to.trim() : `0x${args.swap.to.trim()}`
826
+ );
827
+ const dataHex = (() => {
828
+ const d = (args.swap.data ?? "0x").toString().trim();
829
+ return d.startsWith("0x") ? d : `0x${d}`;
830
+ })();
831
+ const valueWei = nativeEthInPayableValueWei({
832
+ swap: args.swap,
833
+ dataHex,
834
+ quoteInputWei
835
+ });
836
+ if (valueWei <= 0n) {
837
+ throw new Error(
838
+ "Native (ETH) in swap: could not determine payable value (no `swap.value`, no `execute` amount in calldata, and quote input is zero). Refresh the quote and request /swap again."
839
+ );
840
+ }
841
+ const ch = defineChain({
842
+ id: args.chainId,
843
+ name: "Destination",
844
+ nativeCurrency: { decimals: 18, name: "Ether", symbol: "ETH" },
845
+ rpcUrls: { default: { http: [args.rpcUrl] } }
846
+ });
847
+ const publicClient = createPublicClient({ chain: ch, transport: http(args.rpcUrl) });
848
+ const feeParams = await fetchChainFeeParams(args.rpcUrl, args.chainId);
849
+ const legacy = Boolean(args.chainDetail?.legacy) || !feeParams.isEip1559;
850
+ const latestBaseFeeWeiNativeIn = !legacy ? (await publicClient.getBlock({ blockTag: "latest" })).baseFeePerGas ?? 0n : 0n;
851
+ const useCustomGas = args.useCustomGas;
852
+ const chainGasLimitRouter = args.chainDetail?.gasLimit != null && Number.isFinite(Number(args.chainDetail.gasLimit)) && Number(args.chainDetail.gasLimit) > 0 ? Number(args.chainDetail.gasLimit) : void 0;
853
+ const gasFeeMultiplier = useCustomGas && args.chainDetail?.gasMultiplier != null ? Number(args.chainDetail.gasMultiplier) : void 0;
854
+ const nonce = await publicClient.getTransactionCount({ address: args.executorAddress, blockTag: "pending" });
855
+ const proposalTxParamsBatch = [];
856
+ const messageHashes = [];
857
+ const messageRawBatch = [];
858
+ const swapRecord = args.swap;
859
+ const fromTradeApi = parseOptionalGasLimitString(swapRecord.gasLimit) ?? parseOptionalGasLimitString(swapRecord.gas);
860
+ let gasBuildSource = "rpcEstimate";
861
+ let estimateGasError;
862
+ let baseGasUnits1;
863
+ if (fromTradeApi != null && fromTradeApi > 0n) {
864
+ baseGasUnits1 = fromTradeApi;
865
+ gasBuildSource = "tradeApi";
866
+ } else {
867
+ try {
868
+ baseGasUnits1 = await publicClient.estimateGas({
869
+ to: toRouter,
870
+ data: dataHex,
871
+ value: valueWei,
872
+ account: args.executorAddress
873
+ });
874
+ gasBuildSource = "rpcEstimate";
875
+ } catch (e) {
876
+ estimateGasError = e instanceof Error ? e.message : String(e);
877
+ const minRouterGas = 500000n;
878
+ if (useCustomGas) {
879
+ const cfg = args.chainDetail?.gasLimit != null ? parseOptionalGasLimitString(String(args.chainDetail.gasLimit)) : null;
880
+ if (cfg != null && cfg >= minRouterGas) {
881
+ baseGasUnits1 = cfg;
882
+ } else {
883
+ baseGasUnits1 = DEFAULT_UNIVERSAL_ROUTER_GAS_FALLBACK;
884
+ }
885
+ } else {
886
+ baseGasUnits1 = DEFAULT_UNIVERSAL_ROUTER_GAS_FALLBACK;
887
+ }
888
+ gasBuildSource = "estimateFailedFallback";
889
+ }
890
+ }
891
+ const gasLimit1 = routerSwapGasLimitFromEstimate(baseGasUnits1, chainGasLimitRouter);
892
+ const currentNonce0 = nonce;
893
+ let firstTxFeePayload = {};
894
+ if (legacy) {
895
+ let gasPriceWei1 = await publicClient.getGasPrice();
896
+ if (useCustomGas && gasFeeMultiplier != null && gasFeeMultiplier > 0) {
897
+ gasPriceWei1 = gasPriceWei1 * BigInt(100 + gasFeeMultiplier) / 100n;
898
+ }
899
+ if (useCustomGas && args.chainDetail?.gasPrice != null && args.chainDetail.gasPrice > 0) {
900
+ const configured = parseGwei(gweiToDecimalString(Number(args.chainDetail.gasPrice)));
901
+ if (configured > gasPriceWei1) gasPriceWei1 = configured;
902
+ }
903
+ firstTxFeePayload = { txNonce: nonce, txGasLimit: gasLimit1.toString(), txGasPrice: gasPriceWei1.toString() };
904
+ const ser0 = serializeTransaction({
905
+ type: "legacy",
906
+ to: toRouter,
907
+ data: dataHex,
908
+ value: valueWei,
909
+ gas: gasLimit1,
910
+ gasPrice: gasPriceWei1,
911
+ nonce: currentNonce0,
912
+ chainId: args.chainId
913
+ });
914
+ const h0 = keccak256(ser0);
915
+ messageHashes.push(h0.startsWith("0x") ? h0.slice(2) : h0);
916
+ messageRawBatch.push(ser0);
917
+ proposalTxParamsBatch.push({
918
+ nonce: currentNonce0,
919
+ gasLimit: gasLimit1.toString(),
920
+ txType: "legacy",
921
+ gasPrice: gasPriceWei1.toString()
922
+ });
923
+ } else {
924
+ const fetchedBase1 = feeParams.baseFeeGwei ?? 0;
925
+ const fetchedPriority1 = feeParams.priorityFeeGwei ?? 0;
926
+ const configuredBase1 = useCustomGas && args.chainDetail?.baseFee != null ? Number(args.chainDetail.baseFee) : 0;
927
+ const configuredPriority1 = useCustomGas && args.chainDetail?.priorityFee != null ? Number(args.chainDetail.priorityFee) : 0;
928
+ const effectiveBaseFeeGwei1 = Math.max(fetchedBase1, configuredBase1);
929
+ const effectivePriorityFeeGwei1 = Math.max(fetchedPriority1, configuredPriority1);
930
+ const baseFeeMultiplierPct1 = useCustomGas && args.chainDetail?.baseFeeMultiplier != null ? Math.max(100, Number(args.chainDetail.baseFeeMultiplier)) : 100;
931
+ const baseComponentGwei1 = effectiveBaseFeeGwei1 * baseFeeMultiplierPct1 / 100;
932
+ const maxFeePerGasGwei1 = baseComponentGwei1 + effectivePriorityFeeGwei1;
933
+ let maxPriorityFeePerGas1 = effectivePriorityFeeGwei1 > 0 ? parseGwei(gweiToDecimalString(effectivePriorityFeeGwei1)) : parseGwei("1");
934
+ let maxFeePerGas1 = parseGwei(gweiToDecimalString(maxFeePerGasGwei1));
935
+ if (useCustomGas && gasFeeMultiplier != null && gasFeeMultiplier > 0) {
936
+ maxPriorityFeePerGas1 = maxPriorityFeePerGas1 * BigInt(100 + gasFeeMultiplier) / 100n;
937
+ maxFeePerGas1 = maxFeePerGas1 * BigInt(100 + gasFeeMultiplier) / 100n;
938
+ }
939
+ ({ maxFeePerGas: maxFeePerGas1, maxPriorityFeePerGas: maxPriorityFeePerGas1 } = alignEip1559FeesWithLatestBase(maxFeePerGas1, maxPriorityFeePerGas1, latestBaseFeeWeiNativeIn));
940
+ firstTxFeePayload = {
941
+ txNonce: nonce,
942
+ txGasLimit: gasLimit1.toString(),
943
+ txMaxFeePerGas: maxFeePerGas1.toString(),
944
+ txMaxPriorityFeePerGas: maxPriorityFeePerGas1.toString()
945
+ };
946
+ const ser0 = serializeTransaction({
947
+ type: "eip1559",
948
+ to: toRouter,
949
+ data: dataHex,
950
+ value: valueWei,
951
+ gas: gasLimit1,
952
+ maxFeePerGas: maxFeePerGas1,
953
+ maxPriorityFeePerGas: maxPriorityFeePerGas1,
954
+ nonce: currentNonce0,
955
+ chainId: args.chainId
956
+ });
957
+ const h0 = keccak256(ser0);
958
+ messageHashes.push(h0.startsWith("0x") ? h0.slice(2) : h0);
959
+ messageRawBatch.push(ser0);
960
+ proposalTxParamsBatch.push({
961
+ nonce: currentNonce0,
962
+ gasLimit: gasLimit1.toString(),
963
+ txType: "eip1559",
964
+ maxFeePerGas: maxFeePerGas1.toString(),
965
+ maxPriorityFeePerGas: maxPriorityFeePerGas1.toString()
966
+ });
967
+ }
968
+ const dataNo0x = dataHex.startsWith("0x") ? dataHex.slice(2) : dataHex;
969
+ const audit = {
970
+ skipPermit2Batch: true,
971
+ inputKind: "native_eth",
972
+ noErc20Approve: true,
973
+ quoteInputWei: quoteInputWei.toString(),
974
+ swapValueWei: valueWei.toString(),
975
+ uniswapCreateSwap: {
976
+ requestId: args.createSwapResponse.requestId,
977
+ gasFee: args.createSwapResponse.gasFee,
978
+ gasBuildSwap: {
979
+ useCustomGas,
980
+ source: gasBuildSource,
981
+ baseGasUnits: baseGasUnits1.toString(),
982
+ ...estimateGasError != null && estimateGasError !== "" ? { estimateGasError } : {}
983
+ },
984
+ swap: {
985
+ to: args.createSwapResponse.swap.to,
986
+ value: args.createSwapResponse.swap.value,
987
+ dataNibbles: (() => {
988
+ const t = (args.createSwapResponse.swap.data ?? "").toString().trim();
989
+ return t.startsWith("0x") ? t.length - 2 : t.length;
990
+ })()
991
+ }
992
+ },
993
+ fullQuoteFromPermitSnapshot: args.fullQuoteSnapshot,
994
+ originalPurpose: args.purposeText
995
+ };
996
+ const batchMeta = [
997
+ {
998
+ destinationAddress: toRouter,
999
+ signatureText: JSON.stringify({
1000
+ kind: "UniswapV4",
1001
+ name: "UniversalRouter (payable, native in)",
1002
+ note: "Single tx from Trade POST /swap; no ERC-20 approve. Calldata in messageHashes[0] / messageRawBatch[0]."
1003
+ }),
1004
+ evm: { type: "uniswap_v4_swap_tx", version: 1, chainId: String(args.chainId) },
1005
+ uniswapV4: audit
1006
+ }
1007
+ ];
1008
+ const extraPayload = { batchMeta };
1009
+ if (useCustomGas) {
1010
+ const snap = args.customGasChainDetails;
1011
+ if (snap && typeof snap === "object" && !Array.isArray(snap) && Object.keys(snap).length > 0) {
1012
+ extraPayload.customGasChainDetails = snap;
1013
+ }
1014
+ }
1015
+ const extraJSON = JSON.stringify(extraPayload);
1016
+ const firstSigText = batchMeta[0].signatureText;
1017
+ const bodyForSign = {
1018
+ keyList,
1019
+ pubKey: ph,
1020
+ msgHash: messageHashes[0],
1021
+ msgRaw: dataNo0x,
1022
+ messageHashes,
1023
+ messageRawBatch,
1024
+ destinationChainID: String(args.chainId),
1025
+ destinationAddress: toRouter,
1026
+ extraJSON,
1027
+ signatureText: firstSigText,
1028
+ purpose: (() => {
1029
+ const t = args.purposeText.trim();
1030
+ const batchDesc = "Uniswap V4: 1-tx batch \u2014 native gas token in (no ERC-20 approve) \u2014 single payable swap (Trade /swap).";
1031
+ return (t ? `${t}
1032
+
1033
+ ` : "") + batchDesc;
1034
+ })(),
1035
+ ...firstTxFeePayload,
1036
+ proposalTxParams: proposalTxParamsBatch
1037
+ };
1038
+ if (valueWei > 0n) {
1039
+ bodyForSign.value = valueWei.toString();
1040
+ }
1041
+ if (clientId) bodyForSign.clientId = clientId;
1042
+ return { bodyForSign, messageToSign: JSON.stringify(bodyForSign) };
1043
+ }
1044
+ async function buildEvmMultisignBodyUniswapV4SkipPermit2Batch(args) {
1045
+ const ph = (args.keyGen.pubkeyhex ?? "").trim();
1046
+ if (!ph) throw new Error("keyGen pubKey (pubkeyhex) is required");
1047
+ const keyList = args.keyGen.keylist ?? [];
1048
+ const clientId = firstClientIdFromKeyGen(args.keyGen);
1049
+ const tokenIn = getAddress(args.tokenIn);
1050
+ const parsedInOut = parseUniswapQuoteClassicInOut(args.fullQuoteSnapshot);
1051
+ const quoteInputWei = parsedInOut?.inputWei;
1052
+ if (quoteInputWei == null || quoteInputWei <= 0n) {
1053
+ throw new Error(
1054
+ "Could not read a positive input amount from the quote. Refresh the quote and ensure the Trade API returned classic quote.input.amount."
1055
+ );
1056
+ }
1057
+ if (tokenIn === zeroAddress) {
1058
+ return buildEvmMultisignBodyUniswapV4NativeInOnly(args, quoteInputWei);
1059
+ }
1060
+ const toRouter = getAddress(
1061
+ (args.swap.to ?? "").trim().startsWith("0x") ? args.swap.to.trim() : `0x${args.swap.to.trim()}`
1062
+ );
1063
+ const dataHex = (() => {
1064
+ const d = (args.swap.data ?? "0x").toString().trim();
1065
+ return d.startsWith("0x") ? d : `0x${d}`;
1066
+ })();
1067
+ const canonicalUniversalRouter = getUniswapUniversalRouterSpenderOrThrow(args.chainId);
1068
+ const { permit2Spender, approveWei: baseApproveWei, calldataAmountWei } = permit2SpenderAndApproveWeiFromSwapCalldata(
1069
+ dataHex,
1070
+ tokenIn,
1071
+ quoteInputWei,
1072
+ args.chainId
1073
+ );
1074
+ const approveAmountWei = applySlippagePercentToApproveWei(baseApproveWei, args.slippagePercent);
1075
+ const usePermit2Triple = toRouter.toLowerCase() === permit2Spender.toLowerCase();
1076
+ if (usePermit2Triple && approveAmountWei > MAX_UINT160) {
1077
+ throw new Error("Approved transfer amount exceeds allowance-hub uint160 max; reduce trade size.");
1078
+ }
1079
+ let expiration48 = 0n;
1080
+ if (usePermit2Triple) {
1081
+ const deadlineSec = Math.floor(Number(args.swapDeadlineUnix));
1082
+ if (!Number.isFinite(deadlineSec) || deadlineSec <= 0) {
1083
+ throw new Error("Invalid swapDeadlineUnix for allowance-hub approve expiration.");
1084
+ }
1085
+ expiration48 = BigInt(deadlineSec);
1086
+ if (expiration48 > MAX_UINT48) expiration48 = MAX_UINT48;
1087
+ }
1088
+ const erc20ApproveSpender = usePermit2Triple ? PERMIT2_ADDRESS : toRouter;
1089
+ const approveData = encodeFunctionData({
1090
+ abi: erc20Abi,
1091
+ functionName: "approve",
1092
+ args: [erc20ApproveSpender, approveAmountWei]
1093
+ });
1094
+ const valueWei = (() => {
1095
+ const v = args.swap.value;
1096
+ if (v == null || v === "") return 0n;
1097
+ try {
1098
+ return BigInt(String(v).replace(/^0x/i, ""));
1099
+ } catch {
1100
+ return 0n;
1101
+ }
1102
+ })();
1103
+ const ch = defineChain({
1104
+ id: args.chainId,
1105
+ name: "Destination",
1106
+ nativeCurrency: { decimals: 18, name: "Ether", symbol: "ETH" },
1107
+ rpcUrls: { default: { http: [args.rpcUrl] } }
1108
+ });
1109
+ const publicClient = createPublicClient({ chain: ch, transport: http(args.rpcUrl) });
1110
+ const feeParams = await fetchChainFeeParams(args.rpcUrl, args.chainId);
1111
+ const legacy = Boolean(args.chainDetail?.legacy) || !feeParams.isEip1559;
1112
+ const latestBaseFeeWeiSkipBatch = !legacy ? (await publicClient.getBlock({ blockTag: "latest" })).baseFeePerGas ?? 0n : 0n;
1113
+ const useCustomGas = args.useCustomGas;
1114
+ const gasLimitConfig = useCustomGas && args.chainDetail?.gasLimit != null ? Number(args.chainDetail.gasLimit) : void 0;
1115
+ const chainGasLimitRouter = args.chainDetail?.gasLimit != null && Number.isFinite(Number(args.chainDetail.gasLimit)) && Number(args.chainDetail.gasLimit) > 0 ? Number(args.chainDetail.gasLimit) : void 0;
1116
+ const gasFeeMultiplier = useCustomGas && args.chainDetail?.gasMultiplier != null ? Number(args.chainDetail.gasMultiplier) : void 0;
1117
+ const nonce = await publicClient.getTransactionCount({ address: args.executorAddress, blockTag: "pending" });
1118
+ const proposalTxParamsBatch = [];
1119
+ const messageHashes = [];
1120
+ const messageRawBatch = [];
1121
+ const approveMsgRawNo0x = approveData.startsWith("0x") ? approveData.slice(2) : approveData;
1122
+ let firstTxFeePayload = {};
1123
+ const approveGas = await publicClient.estimateGas({
1124
+ to: tokenIn,
1125
+ data: approveData,
1126
+ value: 0n,
1127
+ account: args.executorAddress
1128
+ });
1129
+ const gasLimit0 = useCustomGas ? gasLimitFromEstimateAndChainConfig(approveGas, gasLimitConfig) : approveGas;
1130
+ const currentNonce0 = nonce;
1131
+ if (legacy) {
1132
+ let gasPriceWei = await publicClient.getGasPrice();
1133
+ if (useCustomGas && gasFeeMultiplier != null && gasFeeMultiplier > 0) {
1134
+ gasPriceWei = gasPriceWei * BigInt(100 + gasFeeMultiplier) / 100n;
1135
+ }
1136
+ if (useCustomGas && args.chainDetail?.gasPrice != null && args.chainDetail.gasPrice > 0) {
1137
+ const configured = parseGwei(gweiToDecimalString(Number(args.chainDetail.gasPrice)));
1138
+ if (configured > gasPriceWei) gasPriceWei = configured;
1139
+ }
1140
+ firstTxFeePayload = { txNonce: nonce, txGasLimit: gasLimit0.toString(), txGasPrice: gasPriceWei.toString() };
1141
+ const ser0 = serializeTransaction({
1142
+ type: "legacy",
1143
+ to: tokenIn,
1144
+ data: approveData,
1145
+ value: 0n,
1146
+ gas: gasLimit0,
1147
+ gasPrice: gasPriceWei,
1148
+ nonce: currentNonce0,
1149
+ chainId: args.chainId
1150
+ });
1151
+ const h0 = keccak256(ser0);
1152
+ messageHashes.push(h0.startsWith("0x") ? h0.slice(2) : h0);
1153
+ messageRawBatch.push(ser0);
1154
+ proposalTxParamsBatch.push({
1155
+ nonce: currentNonce0,
1156
+ gasLimit: gasLimit0.toString(),
1157
+ txType: "legacy",
1158
+ gasPrice: gasPriceWei.toString()
1159
+ });
1160
+ } else {
1161
+ const fetchedBase = feeParams.baseFeeGwei ?? 0;
1162
+ const fetchedPriority = feeParams.priorityFeeGwei ?? 0;
1163
+ const configuredBase = useCustomGas && args.chainDetail?.baseFee != null ? Number(args.chainDetail.baseFee) : 0;
1164
+ const configuredPriority = useCustomGas && args.chainDetail?.priorityFee != null ? Number(args.chainDetail.priorityFee) : 0;
1165
+ const effectiveBaseFeeGwei = Math.max(fetchedBase, configuredBase);
1166
+ const effectivePriorityFeeGwei = Math.max(fetchedPriority, configuredPriority);
1167
+ const baseFeeMultiplierPct = useCustomGas && args.chainDetail?.baseFeeMultiplier != null ? Math.max(100, Number(args.chainDetail.baseFeeMultiplier)) : 100;
1168
+ const baseComponentGwei = effectiveBaseFeeGwei * baseFeeMultiplierPct / 100;
1169
+ const maxFeePerGasGwei = baseComponentGwei + effectivePriorityFeeGwei;
1170
+ let maxPriorityFeePerGas = effectivePriorityFeeGwei > 0 ? parseGwei(gweiToDecimalString(effectivePriorityFeeGwei)) : parseGwei("1");
1171
+ let maxFeePerGas = parseGwei(gweiToDecimalString(maxFeePerGasGwei));
1172
+ if (useCustomGas && gasFeeMultiplier != null && gasFeeMultiplier > 0) {
1173
+ maxPriorityFeePerGas = maxPriorityFeePerGas * BigInt(100 + gasFeeMultiplier) / 100n;
1174
+ maxFeePerGas = maxFeePerGas * BigInt(100 + gasFeeMultiplier) / 100n;
1175
+ }
1176
+ ({ maxFeePerGas, maxPriorityFeePerGas } = alignEip1559FeesWithLatestBase(
1177
+ maxFeePerGas,
1178
+ maxPriorityFeePerGas,
1179
+ latestBaseFeeWeiSkipBatch
1180
+ ));
1181
+ firstTxFeePayload = {
1182
+ txNonce: nonce,
1183
+ txGasLimit: gasLimit0.toString(),
1184
+ txMaxFeePerGas: maxFeePerGas.toString(),
1185
+ txMaxPriorityFeePerGas: maxPriorityFeePerGas.toString()
1186
+ };
1187
+ const ser0 = serializeTransaction({
1188
+ type: "eip1559",
1189
+ to: tokenIn,
1190
+ data: approveData,
1191
+ value: 0n,
1192
+ gas: gasLimit0,
1193
+ maxFeePerGas,
1194
+ maxPriorityFeePerGas,
1195
+ nonce: currentNonce0,
1196
+ chainId: args.chainId
1197
+ });
1198
+ const h0 = keccak256(ser0);
1199
+ messageHashes.push(h0.startsWith("0x") ? h0.slice(2) : h0);
1200
+ messageRawBatch.push(ser0);
1201
+ proposalTxParamsBatch.push({
1202
+ nonce: currentNonce0,
1203
+ gasLimit: gasLimit0.toString(),
1204
+ txType: "eip1559",
1205
+ maxFeePerGas: maxFeePerGas.toString(),
1206
+ maxPriorityFeePerGas: maxPriorityFeePerGas.toString()
1207
+ });
1208
+ }
1209
+ if (usePermit2Triple) {
1210
+ const permit2ApproveData = encodeFunctionData({
1211
+ abi: permit2ApproveRouterAbi,
1212
+ functionName: "approve",
1213
+ args: [tokenIn, permit2Spender, approveAmountWei, Number(expiration48)]
1214
+ });
1215
+ const approveP2Gas = await publicClient.estimateGas({
1216
+ to: PERMIT2_ADDRESS,
1217
+ data: permit2ApproveData,
1218
+ value: 0n,
1219
+ account: args.executorAddress
1220
+ });
1221
+ const gasLimitP2 = useCustomGas ? gasLimitFromEstimateAndChainConfig(approveP2Gas, gasLimitConfig) : approveP2Gas;
1222
+ const currentNonceP2 = nonce + 1;
1223
+ if (legacy) {
1224
+ let gasPriceWeiP2 = await publicClient.getGasPrice();
1225
+ if (useCustomGas && gasFeeMultiplier != null && gasFeeMultiplier > 0) {
1226
+ gasPriceWeiP2 = gasPriceWeiP2 * BigInt(100 + gasFeeMultiplier) / 100n;
1227
+ }
1228
+ if (useCustomGas && args.chainDetail?.gasPrice != null && args.chainDetail.gasPrice > 0) {
1229
+ const configured = parseGwei(gweiToDecimalString(Number(args.chainDetail.gasPrice)));
1230
+ if (configured > gasPriceWeiP2) gasPriceWeiP2 = configured;
1231
+ }
1232
+ const serP2 = serializeTransaction({
1233
+ type: "legacy",
1234
+ to: PERMIT2_ADDRESS,
1235
+ data: permit2ApproveData,
1236
+ value: 0n,
1237
+ gas: gasLimitP2,
1238
+ gasPrice: gasPriceWeiP2,
1239
+ nonce: currentNonceP2,
1240
+ chainId: args.chainId
1241
+ });
1242
+ const hP2 = keccak256(serP2);
1243
+ messageHashes.push(hP2.startsWith("0x") ? hP2.slice(2) : hP2);
1244
+ messageRawBatch.push(serP2);
1245
+ proposalTxParamsBatch.push({
1246
+ nonce: currentNonceP2,
1247
+ gasLimit: gasLimitP2.toString(),
1248
+ txType: "legacy",
1249
+ gasPrice: gasPriceWeiP2.toString()
1250
+ });
1251
+ } else {
1252
+ const fetchedBaseP2 = feeParams.baseFeeGwei ?? 0;
1253
+ const fetchedPriorityP2 = feeParams.priorityFeeGwei ?? 0;
1254
+ const configuredBaseP2 = useCustomGas && args.chainDetail?.baseFee != null ? Number(args.chainDetail.baseFee) : 0;
1255
+ const configuredPriorityP2 = useCustomGas && args.chainDetail?.priorityFee != null ? Number(args.chainDetail.priorityFee) : 0;
1256
+ const effectiveBaseFeeGweiP2 = Math.max(fetchedBaseP2, configuredBaseP2);
1257
+ const effectivePriorityFeeGweiP2 = Math.max(fetchedPriorityP2, configuredPriorityP2);
1258
+ const baseFeeMultiplierPctP2 = useCustomGas && args.chainDetail?.baseFeeMultiplier != null ? Math.max(100, Number(args.chainDetail.baseFeeMultiplier)) : 100;
1259
+ const baseComponentGweiP2 = effectiveBaseFeeGweiP2 * baseFeeMultiplierPctP2 / 100;
1260
+ const maxFeePerGasGweiP2 = baseComponentGweiP2 + effectivePriorityFeeGweiP2;
1261
+ let maxPriorityFeePerGasP2 = effectivePriorityFeeGweiP2 > 0 ? parseGwei(gweiToDecimalString(effectivePriorityFeeGweiP2)) : parseGwei("1");
1262
+ let maxFeePerGasP2 = parseGwei(gweiToDecimalString(maxFeePerGasGweiP2));
1263
+ if (useCustomGas && gasFeeMultiplier != null && gasFeeMultiplier > 0) {
1264
+ maxPriorityFeePerGasP2 = maxPriorityFeePerGasP2 * BigInt(100 + gasFeeMultiplier) / 100n;
1265
+ maxFeePerGasP2 = maxFeePerGasP2 * BigInt(100 + gasFeeMultiplier) / 100n;
1266
+ }
1267
+ ({ maxFeePerGas: maxFeePerGasP2, maxPriorityFeePerGas: maxPriorityFeePerGasP2 } = alignEip1559FeesWithLatestBase(maxFeePerGasP2, maxPriorityFeePerGasP2, latestBaseFeeWeiSkipBatch));
1268
+ const serP2 = serializeTransaction({
1269
+ type: "eip1559",
1270
+ to: PERMIT2_ADDRESS,
1271
+ data: permit2ApproveData,
1272
+ value: 0n,
1273
+ gas: gasLimitP2,
1274
+ maxFeePerGas: maxFeePerGasP2,
1275
+ maxPriorityFeePerGas: maxPriorityFeePerGasP2,
1276
+ nonce: currentNonceP2,
1277
+ chainId: args.chainId
1278
+ });
1279
+ const hP2 = keccak256(serP2);
1280
+ messageHashes.push(hP2.startsWith("0x") ? hP2.slice(2) : hP2);
1281
+ messageRawBatch.push(serP2);
1282
+ proposalTxParamsBatch.push({
1283
+ nonce: currentNonceP2,
1284
+ gasLimit: gasLimitP2.toString(),
1285
+ txType: "eip1559",
1286
+ maxFeePerGas: maxFeePerGasP2.toString(),
1287
+ maxPriorityFeePerGas: maxPriorityFeePerGasP2.toString()
1288
+ });
1289
+ }
1290
+ }
1291
+ const swapRecord = args.swap;
1292
+ const fromTradeApi = parseOptionalGasLimitString(swapRecord.gasLimit) ?? parseOptionalGasLimitString(swapRecord.gas);
1293
+ let gasBuildSource = "rpcEstimate";
1294
+ let estimateGasError;
1295
+ let baseGasUnits1;
1296
+ if (fromTradeApi != null && fromTradeApi > 0n) {
1297
+ baseGasUnits1 = fromTradeApi;
1298
+ gasBuildSource = "tradeApi";
1299
+ } else {
1300
+ try {
1301
+ baseGasUnits1 = await publicClient.estimateGas({
1302
+ to: toRouter,
1303
+ data: dataHex,
1304
+ value: valueWei,
1305
+ account: args.executorAddress
1306
+ });
1307
+ gasBuildSource = "rpcEstimate";
1308
+ } catch (e) {
1309
+ estimateGasError = e instanceof Error ? e.message : String(e);
1310
+ const minRouterGas = 500000n;
1311
+ if (useCustomGas) {
1312
+ const cfg = args.chainDetail?.gasLimit != null ? parseOptionalGasLimitString(String(args.chainDetail.gasLimit)) : null;
1313
+ if (cfg != null && cfg >= minRouterGas) {
1314
+ baseGasUnits1 = cfg;
1315
+ } else {
1316
+ baseGasUnits1 = DEFAULT_UNIVERSAL_ROUTER_GAS_FALLBACK;
1317
+ }
1318
+ } else {
1319
+ baseGasUnits1 = DEFAULT_UNIVERSAL_ROUTER_GAS_FALLBACK;
1320
+ }
1321
+ gasBuildSource = "estimateFailedFallback";
1322
+ }
1323
+ }
1324
+ const gasLimit1 = routerSwapGasLimitFromEstimate(baseGasUnits1, chainGasLimitRouter);
1325
+ const currentNonce1 = nonce + (usePermit2Triple ? 2 : 1);
1326
+ if (legacy) {
1327
+ let gasPriceWei1 = await publicClient.getGasPrice();
1328
+ if (useCustomGas && gasFeeMultiplier != null && gasFeeMultiplier > 0) {
1329
+ gasPriceWei1 = gasPriceWei1 * BigInt(100 + gasFeeMultiplier) / 100n;
1330
+ }
1331
+ if (useCustomGas && args.chainDetail?.gasPrice != null && args.chainDetail.gasPrice > 0) {
1332
+ const configured = parseGwei(gweiToDecimalString(Number(args.chainDetail.gasPrice)));
1333
+ if (configured > gasPriceWei1) gasPriceWei1 = configured;
1334
+ }
1335
+ const ser1 = serializeTransaction({
1336
+ type: "legacy",
1337
+ to: toRouter,
1338
+ data: dataHex,
1339
+ value: valueWei,
1340
+ gas: gasLimit1,
1341
+ gasPrice: gasPriceWei1,
1342
+ nonce: currentNonce1,
1343
+ chainId: args.chainId
1344
+ });
1345
+ const h1 = keccak256(ser1);
1346
+ messageHashes.push(h1.startsWith("0x") ? h1.slice(2) : h1);
1347
+ messageRawBatch.push(ser1);
1348
+ proposalTxParamsBatch.push({
1349
+ nonce: currentNonce1,
1350
+ gasLimit: gasLimit1.toString(),
1351
+ txType: "legacy",
1352
+ gasPrice: gasPriceWei1.toString()
1353
+ });
1354
+ } else {
1355
+ const fetchedBase1 = feeParams.baseFeeGwei ?? 0;
1356
+ const fetchedPriority1 = feeParams.priorityFeeGwei ?? 0;
1357
+ const configuredBase1 = useCustomGas && args.chainDetail?.baseFee != null ? Number(args.chainDetail.baseFee) : 0;
1358
+ const configuredPriority1 = useCustomGas && args.chainDetail?.priorityFee != null ? Number(args.chainDetail.priorityFee) : 0;
1359
+ const effectiveBaseFeeGwei1 = Math.max(fetchedBase1, configuredBase1);
1360
+ const effectivePriorityFeeGwei1 = Math.max(fetchedPriority1, configuredPriority1);
1361
+ const baseFeeMultiplierPct1 = useCustomGas && args.chainDetail?.baseFeeMultiplier != null ? Math.max(100, Number(args.chainDetail.baseFeeMultiplier)) : 100;
1362
+ const baseComponentGwei1 = effectiveBaseFeeGwei1 * baseFeeMultiplierPct1 / 100;
1363
+ const maxFeePerGasGwei1 = baseComponentGwei1 + effectivePriorityFeeGwei1;
1364
+ let maxPriorityFeePerGas1 = effectivePriorityFeeGwei1 > 0 ? parseGwei(gweiToDecimalString(effectivePriorityFeeGwei1)) : parseGwei("1");
1365
+ let maxFeePerGas1 = parseGwei(gweiToDecimalString(maxFeePerGasGwei1));
1366
+ if (useCustomGas && gasFeeMultiplier != null && gasFeeMultiplier > 0) {
1367
+ maxPriorityFeePerGas1 = maxPriorityFeePerGas1 * BigInt(100 + gasFeeMultiplier) / 100n;
1368
+ maxFeePerGas1 = maxFeePerGas1 * BigInt(100 + gasFeeMultiplier) / 100n;
1369
+ }
1370
+ ({ maxFeePerGas: maxFeePerGas1, maxPriorityFeePerGas: maxPriorityFeePerGas1 } = alignEip1559FeesWithLatestBase(maxFeePerGas1, maxPriorityFeePerGas1, latestBaseFeeWeiSkipBatch));
1371
+ const ser1 = serializeTransaction({
1372
+ type: "eip1559",
1373
+ to: toRouter,
1374
+ data: dataHex,
1375
+ value: valueWei,
1376
+ gas: gasLimit1,
1377
+ maxFeePerGas: maxFeePerGas1,
1378
+ maxPriorityFeePerGas: maxPriorityFeePerGas1,
1379
+ nonce: currentNonce1,
1380
+ chainId: args.chainId
1381
+ });
1382
+ const h1 = keccak256(ser1);
1383
+ messageHashes.push(h1.startsWith("0x") ? h1.slice(2) : h1);
1384
+ messageRawBatch.push(ser1);
1385
+ proposalTxParamsBatch.push({
1386
+ nonce: currentNonce1,
1387
+ gasLimit: gasLimit1.toString(),
1388
+ txType: "eip1559",
1389
+ maxFeePerGas: maxFeePerGas1.toString(),
1390
+ maxPriorityFeePerGas: maxPriorityFeePerGas1.toString()
1391
+ });
1392
+ }
1393
+ const swapMsgIndex = usePermit2Triple ? 2 : 1;
1394
+ const audit = {
1395
+ skipPermit2Batch: true,
1396
+ approvalPath: usePermit2Triple ? "permit2_triple" : "dispatcher",
1397
+ approveAmount: {
1398
+ baseWeiBeforeSlippage: baseApproveWei.toString(),
1399
+ finalWei: approveAmountWei.toString(),
1400
+ slippagePercent: args.slippagePercent,
1401
+ quoteInputWei: quoteInputWei.toString(),
1402
+ ...calldataAmountWei != null ? { swapCalldataAmountWei: calldataAmountWei.toString() } : {}
1403
+ },
1404
+ uniswapCreateSwap: {
1405
+ requestId: args.createSwapResponse.requestId,
1406
+ gasFee: args.createSwapResponse.gasFee,
1407
+ gasBuildSwap: {
1408
+ useCustomGas,
1409
+ source: gasBuildSource,
1410
+ baseGasUnits: baseGasUnits1.toString(),
1411
+ ...estimateGasError != null && estimateGasError !== "" ? { estimateGasError } : {}
1412
+ },
1413
+ swap: {
1414
+ to: args.createSwapResponse.swap.to,
1415
+ value: args.createSwapResponse.swap.value,
1416
+ dataNibbles: (() => {
1417
+ const d = args.createSwapResponse.swap.data ?? "";
1418
+ const t = d.toString().trim();
1419
+ return t.startsWith("0x") ? t.length - 2 : t.length;
1420
+ })()
1421
+ }
1422
+ },
1423
+ fullQuoteFromPermitSnapshot: args.fullQuoteSnapshot,
1424
+ originalPurpose: args.purposeText,
1425
+ innerRouterFromCalldata: permit2Spender,
1426
+ swapToEqualsInnerRouter: usePermit2Triple,
1427
+ ...usePermit2Triple ? {
1428
+ permit2Erc20Approve: {
1429
+ token: tokenIn,
1430
+ spender: PERMIT2_ADDRESS,
1431
+ amountWei: approveAmountWei.toString(),
1432
+ note: "ERC-20 \u2192 allowance hub before hub approve(router)."
1433
+ },
1434
+ permit2ApproveUniversalRouter: {
1435
+ permit2: PERMIT2_ADDRESS,
1436
+ token: tokenIn,
1437
+ spender: permit2Spender,
1438
+ canonicalUniversalRouterFromAppMap: canonicalUniversalRouter,
1439
+ spenderMatchesCanonicalMap: permit2Spender.toLowerCase() === canonicalUniversalRouter.toLowerCase(),
1440
+ amountWei: approveAmountWei.toString(),
1441
+ expiration: expiration48.toString(),
1442
+ note: "Allowance-hub spender must match the router in swap calldata."
1443
+ }
1444
+ } : {
1445
+ erc20ApproveDispatcher: {
1446
+ token: tokenIn,
1447
+ spender: toRouter,
1448
+ amountWei: approveAmountWei.toString(),
1449
+ note: "Dispatcher pulls via ERC20.transferFrom(user, universalRouter, amount); allowance must be on swap.to (see cast trace TRANSFER_FROM_FAILED when only the hub is approved)."
1450
+ }
1451
+ }
1452
+ };
1453
+ const batchMeta = [
1454
+ {
1455
+ destinationAddress: tokenIn,
1456
+ signatureText: JSON.stringify({
1457
+ kind: "UniswapV4",
1458
+ name: "ERC20.approve",
1459
+ to: usePermit2Triple ? "allowance hub" : "dispatcher(swap.to)",
1460
+ function: "approve(address spender, uint256 amount)",
1461
+ spender: erc20ApproveSpender,
1462
+ amountWei: approveAmountWei.toString()
1463
+ }),
1464
+ evm: {
1465
+ type: usePermit2Triple ? "uniswap_v4_skip_permit2_approve" : "uniswap_v4_skip_permit2_dispatcher_approve",
1466
+ version: 1,
1467
+ chainId: String(args.chainId),
1468
+ ...usePermit2Triple ? { permit2: PERMIT2_ADDRESS } : { dispatcher: toRouter }
1469
+ }
1470
+ }
1471
+ ];
1472
+ if (usePermit2Triple) {
1473
+ batchMeta.push({
1474
+ destinationAddress: PERMIT2_ADDRESS,
1475
+ signatureText: JSON.stringify({
1476
+ kind: "UniswapV4",
1477
+ name: "AllowanceHub.approve",
1478
+ function: "approve(address token, address spender, uint160 amount, uint48 expiration)",
1479
+ token: tokenIn,
1480
+ spender: permit2Spender,
1481
+ amountWei: approveAmountWei.toString(),
1482
+ expiration: expiration48.toString()
1483
+ }),
1484
+ evm: {
1485
+ type: "uniswap_v4_skip_permit2_permit2_approve",
1486
+ version: 1,
1487
+ chainId: String(args.chainId),
1488
+ permit2: PERMIT2_ADDRESS,
1489
+ permit2Spender
1490
+ }
1491
+ });
1492
+ }
1493
+ batchMeta.push({
1494
+ destinationAddress: toRouter,
1495
+ signatureText: JSON.stringify({
1496
+ kind: "UniswapV4",
1497
+ name: "UniversalRouter.execute",
1498
+ note: `Calldata from Trade API POST /swap; signed tx hash in messageHashes[${swapMsgIndex}].`
1499
+ }),
1500
+ evm: { type: "uniswap_v4_swap_tx", version: 1, chainId: String(args.chainId) },
1501
+ uniswapV4: audit
1502
+ });
1503
+ const extraPayload = { batchMeta };
1504
+ if (useCustomGas) {
1505
+ const snap = args.customGasChainDetails;
1506
+ if (snap && typeof snap === "object" && !Array.isArray(snap) && Object.keys(snap).length > 0) {
1507
+ extraPayload.customGasChainDetails = snap;
1508
+ }
1509
+ }
1510
+ const extraJSON = JSON.stringify(extraPayload);
1511
+ const firstSigText = batchMeta[0].signatureText;
1512
+ const bodyForSign = {
1513
+ keyList,
1514
+ pubKey: ph,
1515
+ msgHash: messageHashes[0],
1516
+ msgRaw: approveMsgRawNo0x,
1517
+ messageHashes,
1518
+ messageRawBatch,
1519
+ destinationChainID: String(args.chainId),
1520
+ destinationAddress: tokenIn,
1521
+ extraJSON,
1522
+ signatureText: firstSigText,
1523
+ purpose: (() => {
1524
+ const t = args.purposeText.trim();
1525
+ const batchDesc = usePermit2Triple ? "Uniswap V4: 3-tx batch (classic allowance) \u2014 (1) ERC-20 approve allowance hub, (2) hub approve(Universal Router), (3) swap (Trade /swap)." : "Uniswap V4: 2-tx batch (classic allowance, dispatcher) \u2014 (1) ERC-20 approve swap.to (dispatcher pulls tokens), (2) swap (Trade /swap).";
1526
+ return (t ? `${t}
1527
+
1528
+ ` : "") + batchDesc;
1529
+ })(),
1530
+ ...firstTxFeePayload,
1531
+ proposalTxParams: proposalTxParamsBatch
1532
+ };
1533
+ if (valueWei > 0n) {
1534
+ bodyForSign.value = valueWei.toString();
1535
+ }
1536
+ if (clientId) bodyForSign.clientId = clientId;
1537
+ return { bodyForSign, messageToSign: JSON.stringify(bodyForSign) };
1538
+ }
1539
+
1540
+ // src/protocols/evm/uniswap-v4/support.ts
1541
+ function computeUniswapV4Session(args) {
1542
+ const chainIdNum = typeof args.assetsChainId === "number" ? args.assetsChainId : parseInt(String(args.assetsChainId).trim(), 10);
1543
+ const rpcUrl = (args.rpcUrl ?? "").trim();
1544
+ const chainSupported = args.protocolEnabled && Number.isFinite(chainIdNum) && chainIdNum > 0 && rpcUrl.length > 0 && isUniswapV4ChainSupported(chainIdNum);
1545
+ let universalRouter;
1546
+ if (chainSupported) {
1547
+ try {
1548
+ universalRouter = getUniswapUniversalRouterSpenderOrThrow(chainIdNum);
1549
+ } catch {
1550
+ universalRouter = void 0;
1551
+ }
1552
+ }
1553
+ return {
1554
+ chainId: chainIdNum,
1555
+ rpcUrl,
1556
+ chainSupported,
1557
+ universalRouter
1558
+ };
1559
+ }
1560
+
1561
+ // src/protocols/evm/uniswap-v4/swap.ts
1562
+ async function swapFromQuote(args) {
1563
+ const deadline = args.swapTransactionDeadlineUnix ?? swapTransactionDeadlineUnixFromExpiryMinutes(30);
1564
+ return buildEvmMultisignBodyUniswapV4SkipPermit2Batch({
1565
+ keyGen: args.keyGen,
1566
+ chainId: args.chainId,
1567
+ rpcUrl: args.rpcUrl,
1568
+ chainDetail: args.chainDetail,
1569
+ useCustomGas: args.useCustomGas,
1570
+ customGasChainDetails: args.customGasChainDetails,
1571
+ tokenIn: args.tokenIn,
1572
+ executorAddress: args.executorAddress,
1573
+ swap: args.swap.swap,
1574
+ createSwapResponse: args.swap,
1575
+ fullQuoteSnapshot: args.fullQuote,
1576
+ purposeText: args.purposeText,
1577
+ swapDeadlineUnix: deadline,
1578
+ slippagePercent: args.slippagePercent
1579
+ });
1580
+ }
1581
+ async function quoteSwap(args) {
1582
+ return uniswapTradeQuote(args);
1583
+ }
1584
+ async function createSwap(args) {
1585
+ return uniswapCreateSwap(args);
1586
+ }
1587
+ async function swapExactInput(args) {
1588
+ const swap = await uniswapCreateSwap({
1589
+ uniswapApiKey: args.uniswapApiKey,
1590
+ fullQuoteFromPermit: args.fullQuote,
1591
+ baseUrl: args.baseUrl,
1592
+ swapTransactionDeadlineUnix: args.swapTransactionDeadlineUnix,
1593
+ useServerProxy: false
1594
+ });
1595
+ return swapFromQuote({ ...args, swap });
1596
+ }
1597
+
1598
+ // src/protocols/evm/uniswap-v4/index.ts
1599
+ var UNISWAP_V4_PROTOCOL_ID = "uniswap-v4";
1600
+ var uniswapV4ProtocolModule = {
1601
+ id: UNISWAP_V4_PROTOCOL_ID,
1602
+ chainCategory: "evm",
1603
+ isChainSupported(ctx) {
1604
+ if (ctx.chainCategory !== "evm") return false;
1605
+ return isUniswapV4ChainSupported(ctx.chainId);
1606
+ },
1607
+ isTokenSupported(token) {
1608
+ if (token.category !== "evm") return false;
1609
+ return token.kind === "native" || token.kind === "erc20";
1610
+ },
1611
+ actions: [
1612
+ {
1613
+ id: "uniswap-v4.swap-exact-input",
1614
+ protocolId: UNISWAP_V4_PROTOCOL_ID,
1615
+ chainCategory: "evm",
1616
+ description: "Swap ERC-20 or native token via Uniswap V4 Universal Router (classic allowance path)",
1617
+ commonParams: ["keyGen", "purposeText", "useCustomGas"],
1618
+ params: {
1619
+ tokenIn: { type: "address", required: true, description: "Input token (0x0 for native)" },
1620
+ tokenOut: { type: "address", required: true, description: "Output token address" },
1621
+ amount: { type: "string", required: true, description: "Human or wei amount per trade type" },
1622
+ slippagePercent: { type: "number", required: true, description: "Slippage tolerance percent" },
1623
+ swapTransactionDeadlineUnix: {
1624
+ type: "number",
1625
+ required: false,
1626
+ description: "On-chain swap deadline (unix seconds)"
1627
+ },
1628
+ uniswapApiKey: { type: "string", required: true, description: "Uniswap Trade API key" }
1629
+ }
1630
+ },
1631
+ {
1632
+ id: "uniswap-v4.quote",
1633
+ protocolId: UNISWAP_V4_PROTOCOL_ID,
1634
+ chainCategory: "evm",
1635
+ description: "Fetch Uniswap Trade API quote",
1636
+ commonParams: ["keyGen"],
1637
+ params: {
1638
+ tokenIn: { type: "address", required: true, description: "Input token" },
1639
+ tokenOut: { type: "address", required: true, description: "Output token" },
1640
+ amount: { type: "string", required: true, description: "Amount for quote" },
1641
+ type: { type: "EXACT_INPUT | EXACT_OUTPUT", required: true, description: "Trade type" }
1642
+ }
1643
+ }
1644
+ ]
1645
+ };
1646
+ registerProtocolModule(uniswapV4ProtocolModule);
1647
+ var uniswapV4 = {
1648
+ quoteSwap,
1649
+ createSwap,
1650
+ swapExactInput,
1651
+ swapFromQuote,
1652
+ buildSwapMultisignBody: buildEvmMultisignBodyUniswapV4SkipPermit2Batch,
1653
+ quote: uniswapTradeQuote,
1654
+ isChainSupported: isUniswapV4ChainSupported
1655
+ };
1656
+
1657
+ export { MAX_UINT160, MAX_UINT48, PERMIT2_ADDRESS, UNISWAP_SWAP_DEFAULT_DEADLINE_SEC_OFFSET, UNISWAP_SWAP_DEFAULT_EXPIRY_MINUTES, UNISWAP_TRADE_BASE_DEFAULT, UNISWAP_UNIVERSAL_ROUTER_DEFAULT_GAS_UNITS, UNISWAP_UNIVERSAL_ROUTER_VERSION_DEFAULT, UNISWAP_V4_PROTOCOL_ID, applySlippagePercentToApproveWei, buildEvmMultisignBodyUniswapV4SkipPermit2Batch, buildUniswapQuoteRequestBody, buildUniswapV4PurposePrefill, computeUniswapV4Session, createSwap, errorMessageFromUniswapJsonBody, fetchEthereumAddressForKeyGen, formatUniswapAmountFieldFromWei, formatUniswapPercentForUi, formatUniswapQuoteForDisplay, getClassicQuoteFromStoredUniswapResponse, getUniswapUniversalRouterSpenderOrThrow, isUniswapFullQuoteResponseNativeIn, isUniswapTokenInAddressNative, isUniswapV4ChainSupported, isUniswapV4SwapEvmSignRequest, messageFromUniswapHttpResponseBody, parseUniswapChainId, parseUniswapQuoteClassicInOut, parseUniswapQuoteSlippageInfo, quoteSwap, resolveRouterSwapGasUnitsFromSignRequest, swapExactInput, swapFromQuote, swapTransactionDeadlineUnixFromExpiryMinutes, uniswapCreateSwap, uniswapQuoteToJsonQuoteOneLine, uniswapTradeQuote, uniswapV4, uniswapV4ProtocolModule };
1658
+ //# sourceMappingURL=index.js.map
1659
+ //# sourceMappingURL=index.js.map