@continuumdao/ctm-mpc-defi 0.1.4 → 0.2.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (94) hide show
  1. package/README.md +20 -78
  2. package/dist/agent/catalog.cjs +1388 -144
  3. package/dist/agent/catalog.cjs.map +1 -1
  4. package/dist/agent/catalog.d.ts +881 -17
  5. package/dist/agent/catalog.js +1327 -145
  6. package/dist/agent/catalog.js.map +1 -1
  7. package/dist/agent/skills/aave-v4/SKILL.md +43 -0
  8. package/dist/agent/skills/curve-dao/SKILL.md +12 -0
  9. package/dist/agent/skills/ethena/SKILL.md +10 -0
  10. package/dist/agent/skills/euler-v2/SKILL.md +10 -0
  11. package/dist/agent/skills/lido/SKILL.md +22 -0
  12. package/dist/agent/skills/maple-syrup/SKILL.md +10 -0
  13. package/dist/agent/skills/sky/SKILL.md +10 -0
  14. package/dist/agent/skills/uniswap-v4/SKILL.md +22 -0
  15. package/dist/chains/evm/index.cjs +27 -213
  16. package/dist/chains/evm/index.cjs.map +1 -1
  17. package/dist/chains/evm/index.d.ts +15 -25
  18. package/dist/chains/evm/index.js +21 -199
  19. package/dist/chains/evm/index.js.map +1 -1
  20. package/dist/chains/near/index.d.ts +1 -1
  21. package/dist/chains/solana/index.d.ts +1 -1
  22. package/dist/core/index.cjs +8 -110
  23. package/dist/core/index.cjs.map +1 -1
  24. package/dist/core/index.d.ts +5 -39
  25. package/dist/core/index.js +6 -100
  26. package/dist/core/index.js.map +1 -1
  27. package/dist/{envelope-CcE5Cz_q.d.ts → envelope-CpBUh9eP.d.ts} +1 -1
  28. package/dist/index.cjs +238 -1184
  29. package/dist/index.cjs.map +1 -1
  30. package/dist/index.d.ts +7 -10
  31. package/dist/index.js +227 -1156
  32. package/dist/index.js.map +1 -1
  33. package/dist/protocols/evm/aave-v4/index.cjs +1710 -0
  34. package/dist/protocols/evm/aave-v4/index.cjs.map +1 -0
  35. package/dist/protocols/evm/aave-v4/index.d.ts +499 -0
  36. package/dist/protocols/evm/aave-v4/index.js +1666 -0
  37. package/dist/protocols/evm/aave-v4/index.js.map +1 -0
  38. package/dist/protocols/evm/curve-dao/index.cjs +24 -124
  39. package/dist/protocols/evm/curve-dao/index.cjs.map +1 -1
  40. package/dist/protocols/evm/curve-dao/index.d.ts +3 -4
  41. package/dist/protocols/evm/curve-dao/index.js +15 -115
  42. package/dist/protocols/evm/curve-dao/index.js.map +1 -1
  43. package/dist/protocols/evm/ethena/index.cjs +853 -0
  44. package/dist/protocols/evm/ethena/index.cjs.map +1 -0
  45. package/dist/protocols/evm/ethena/index.d.ts +160 -0
  46. package/dist/protocols/evm/ethena/index.js +831 -0
  47. package/dist/protocols/evm/ethena/index.js.map +1 -0
  48. package/dist/protocols/evm/euler-v2/index.cjs +1585 -0
  49. package/dist/protocols/evm/euler-v2/index.cjs.map +1 -0
  50. package/dist/protocols/evm/euler-v2/index.d.ts +316 -0
  51. package/dist/protocols/evm/euler-v2/index.js +1560 -0
  52. package/dist/protocols/evm/euler-v2/index.js.map +1 -0
  53. package/dist/protocols/evm/lido/index.cjs +839 -0
  54. package/dist/protocols/evm/lido/index.cjs.map +1 -0
  55. package/dist/protocols/evm/lido/index.d.ts +119 -0
  56. package/dist/protocols/evm/lido/index.js +814 -0
  57. package/dist/protocols/evm/lido/index.js.map +1 -0
  58. package/dist/protocols/evm/maple/index.cjs +619 -0
  59. package/dist/protocols/evm/maple/index.cjs.map +1 -0
  60. package/dist/protocols/evm/maple/index.d.ts +108 -0
  61. package/dist/protocols/evm/maple/index.js +605 -0
  62. package/dist/protocols/evm/maple/index.js.map +1 -0
  63. package/dist/protocols/evm/sky/index.cjs +1259 -0
  64. package/dist/protocols/evm/sky/index.cjs.map +1 -0
  65. package/dist/protocols/evm/sky/index.d.ts +217 -0
  66. package/dist/protocols/evm/sky/index.js +1234 -0
  67. package/dist/protocols/evm/sky/index.js.map +1 -0
  68. package/dist/protocols/evm/uniswap-v4/index.cjs +423 -658
  69. package/dist/protocols/evm/uniswap-v4/index.cjs.map +1 -1
  70. package/dist/protocols/evm/uniswap-v4/index.d.ts +3 -4
  71. package/dist/protocols/evm/uniswap-v4/index.js +422 -657
  72. package/dist/protocols/evm/uniswap-v4/index.js.map +1 -1
  73. package/dist/{registry-oMKlO_5z.d.ts → registry-Bv5o37_w.d.ts} +1 -1
  74. package/dist/{types-Ce2qNHai.d.cts → types-BfjWdw1j.d.ts} +3 -1
  75. package/dist/{types-5u863Fd9.d.ts → types-DUeNJLr9.d.ts} +1 -1
  76. package/package.json +43 -8
  77. package/dist/agent/catalog.d.cts +0 -195
  78. package/dist/chains/evm/index.d.cts +0 -62
  79. package/dist/chains/near/index.d.cts +0 -37
  80. package/dist/chains/solana/index.d.cts +0 -40
  81. package/dist/core/index.d.cts +0 -43
  82. package/dist/envelope-DYDPnrHZ.d.cts +0 -35
  83. package/dist/index.d.cts +0 -15
  84. package/dist/keygen-CfNp8yKJ.d.cts +0 -9
  85. package/dist/keygen-DsINazx8.d.ts +0 -9
  86. package/dist/nodeRead-BnmSaMGO.d.cts +0 -8
  87. package/dist/nodeRead-BnmSaMGO.d.ts +0 -8
  88. package/dist/protocols/evm/curve-dao/index.d.cts +0 -147
  89. package/dist/protocols/evm/uniswap-v4/index.d.cts +0 -324
  90. package/dist/registry-BwZoE668.d.cts +0 -8
  91. package/dist/txParams-BC7ogvdR.d.cts +0 -19
  92. package/dist/txParams-BC7ogvdR.d.ts +0 -19
  93. package/dist/types-B8idm_gu.d.cts +0 -34
  94. package/dist/types-Ce2qNHai.d.ts +0 -57
package/dist/index.js CHANGED
@@ -1,22 +1,8 @@
1
- import { getAddress, zeroAddress, defineChain, createPublicClient, http, formatUnits, parseGwei, serializeTransaction, keccak256, encodeFunctionData, erc20Abi, parseAbi, parseUnits, decodeFunctionData, isAddress } from 'viem';
1
+ import { getClientIdFromKeyGenResult, gasLimitFromEstimateAndChainConfig, fetchChainFeeParams, gweiToDecimalString, proposalTxParamsToFeeSnapshot, alignEip1559FeesWithLatestBase, nodeFetchWithReadAuth } from '@continuumdao/continuum-node-sdk';
2
+ export { getClientIdFromKeyGenResult as firstClientIdFromKeyGen } from '@continuumdao/continuum-node-sdk';
3
+ import { getAddress, zeroAddress, defineChain, createPublicClient, http, parseGwei, serializeTransaction, keccak256, encodeFunctionData, erc20Abi, parseAbi, parseUnits, decodeFunctionData, isAddress } from 'viem';
2
4
 
3
- // src/core/keygen.ts
4
- function firstClientIdFromKeyGen(data) {
5
- const map = data?.ClientKeys;
6
- if (!map || typeof map !== "object") return null;
7
- for (const v of Object.values(map)) {
8
- if (typeof v === "string" && v.trim()) return v.trim();
9
- }
10
- return null;
11
- }
12
- function requirePubKeyHex(keyGen) {
13
- const ph = (keyGen.pubkeyhex ?? "").trim();
14
- if (!ph) throw new Error("keyGen pubKey (pubkeyhex) is required");
15
- return ph;
16
- }
17
- function keyListFromKeyGen(keyGen) {
18
- return keyGen.keylist ?? [];
19
- }
5
+ // src/core/index.ts
20
6
 
21
7
  // src/core/purpose.ts
22
8
  function mergePurposeText(purposeText, purposeSuffix) {
@@ -27,8 +13,6 @@ function mergePurposeText(purposeText, purposeSuffix) {
27
13
 
28
14
  ${suffix}` : suffix;
29
15
  }
30
-
31
- // src/core/envelope.ts
32
16
  function finalizeMultisign(input) {
33
17
  const { keyGen, destinationChainID, legs } = input;
34
18
  if (legs.length === 0) {
@@ -37,7 +21,7 @@ function finalizeMultisign(input) {
37
21
  const ph = (keyGen.pubkeyhex ?? "").trim();
38
22
  if (!ph) throw new Error("keyGen pubKey (pubkeyhex) is required");
39
23
  const keyList = keyGen.keylist ?? [];
40
- const clientId = firstClientIdFromKeyGen(keyGen);
24
+ const clientId = getClientIdFromKeyGenResult(keyGen);
41
25
  const first = legs[0];
42
26
  const messageHashes = legs.map((l) => l.msgHash);
43
27
  const messageRawBatch = legs.map((l) => l.msgRaw);
@@ -102,85 +86,6 @@ function getProtocolModule(id) {
102
86
  function getActionsByChainCategory(category) {
103
87
  return modules.filter((m) => m.chainCategory === category).flatMap((m) => m.actions);
104
88
  }
105
-
106
- // src/core/nodeRead.ts
107
- function nodeFetchWithReadAuth(url, init, auth) {
108
- const method = (init?.method ?? "GET").toUpperCase();
109
- const headers = new Headers(init?.headers);
110
- if (auth.bearerOnGet && method === "GET" && auth.jwt && auth.jwt.trim()) {
111
- headers.set("Authorization", `Bearer ${auth.jwt.trim()}`);
112
- }
113
- return fetch(url, { ...init, headers });
114
- }
115
-
116
- // src/core/managementPostSig.ts
117
- function normalizeManagementNodeKey(nodeKey) {
118
- const nk = nodeKey?.trim().replace(/^0x/i, "");
119
- if (!nk || !/^[0-9a-fA-F]{128}$/.test(nk)) {
120
- throw new Error("nodeKey is required (128 hex from GET /getNodeKey).");
121
- }
122
- return nk.toLowerCase();
123
- }
124
- function managementSigFields(nonce, nodeKey) {
125
- return { nonce, clientSig: "", nodeKey: normalizeManagementNodeKey(nodeKey) };
126
- }
127
- function buildManagementPostBody(nonce, nodeKey, fields = {}) {
128
- return { ...managementSigFields(nonce, nodeKey), ...fields };
129
- }
130
- function messageToSignManagementBody(body) {
131
- return JSON.stringify({ ...body, clientSig: "" });
132
- }
133
- function withManagementClientSig(body, clientSig) {
134
- return { ...body, clientSig: clientSig.trim().replace(/^0x/i, "") };
135
- }
136
-
137
- // src/core/managementNonce.ts
138
- function mpcAuthData(raw) {
139
- const code = raw.Code ?? raw.code;
140
- if (code !== 0 && code !== void 0) return void 0;
141
- return raw.Data ?? raw.data;
142
- }
143
- async function fetchNodeKey(nodeUrl, readAuth = { bearerOnGet: false, jwt: null }) {
144
- const base = nodeUrl.trim().replace(/\/$/, "");
145
- const res = await nodeFetchWithReadAuth(`${base}/getNodeKey`, { cache: "no-store" }, readAuth);
146
- const text = await res.text();
147
- let raw;
148
- try {
149
- raw = JSON.parse(text);
150
- } catch {
151
- return { nodeKey: "", ok: false };
152
- }
153
- const data = mpcAuthData(raw);
154
- const nk = typeof data === "string" ? data : data != null && typeof data === "object" && !Array.isArray(data) ? String(data.nodeKey ?? data.NodeKey ?? "") : data != null ? String(data) : "";
155
- const trimmed = nk.trim().replace(/^0x/i, "");
156
- if (!/^[0-9a-fA-F]{128}$/.test(trimmed)) {
157
- return { nodeKey: "", ok: false };
158
- }
159
- return { nodeKey: trimmed.toLowerCase(), ok: res.ok };
160
- }
161
- async function fetchManagementNonce(nodeUrl, useEd25519, ed25519PublicKey, readAuth = { bearerOnGet: false, jwt: null }) {
162
- const base = nodeUrl.trim().replace(/\/$/, "");
163
- const path = useEd25519 ? "/getPublicMgtKeyNonce" : "/getNodeMgtKeyNonce";
164
- const url = useEd25519 && ed25519PublicKey && /^[0-9a-fA-F]{64}$/.test(ed25519PublicKey.trim()) ? `${base}${path}?publicKey=${encodeURIComponent(ed25519PublicKey.trim())}` : `${base}${path}`;
165
- const res = await nodeFetchWithReadAuth(url, { cache: "no-store" }, readAuth);
166
- const text = await res.text();
167
- let raw;
168
- try {
169
- raw = JSON.parse(text);
170
- } catch {
171
- return { nonce: 0, ok: false, code: -1 };
172
- }
173
- const code = raw.Code ?? raw.code;
174
- const payload = mpcAuthData(raw) ?? raw.Data ?? raw.data;
175
- let nonce = 0;
176
- if (typeof payload === "number" && !Number.isNaN(payload)) {
177
- nonce = payload;
178
- } else if (payload && typeof payload === "object") {
179
- const n = payload.nonce ?? payload.Nonce;
180
- nonce = typeof n === "number" && !Number.isNaN(n) ? n : Number(n) || 0;
181
- }
182
- return { nonce, ok: res.ok && (code === 0 || code === void 0), code: code ?? -1 };
183
- }
184
89
  function isEvmNativeToken(address) {
185
90
  try {
186
91
  return getAddress(address) === zeroAddress;
@@ -195,161 +100,12 @@ function matchEvmTokenKind(kind, address) {
195
100
  }
196
101
  return true;
197
102
  }
198
-
199
- // src/chains/evm/txParams.ts
200
- function gasLimitFromEstimateAndChainConfig(estimatedGas, chainGasLimit) {
201
- if (chainGasLimit == null || !Number.isFinite(chainGasLimit) || chainGasLimit <= 0) {
202
- return estimatedGas;
203
- }
204
- const cfg = BigInt(Math.floor(chainGasLimit));
205
- return cfg > estimatedGas ? cfg : estimatedGas;
206
- }
207
103
  function routerSwapGasLimitFromEstimate(estimatedGas, chainGasLimit) {
208
104
  if (chainGasLimit != null && Number.isFinite(chainGasLimit) && chainGasLimit > 0) {
209
105
  return gasLimitFromEstimateAndChainConfig(estimatedGas, chainGasLimit);
210
106
  }
211
107
  return (estimatedGas * 12n + 9n) / 10n;
212
108
  }
213
- function composeFeePayloadToTxParams(p, legacy) {
214
- const gl = p.txGasLimit ?? p.txgaslimit;
215
- if (gl == null || String(gl).trim() === "") return void 0;
216
- const n = p.txNonce ?? p.txnonce;
217
- let nonce = 0;
218
- if (typeof n === "bigint") nonce = Number(n);
219
- else if (typeof n === "number") nonce = n;
220
- else if (n != null) nonce = parseInt(String(n), 10);
221
- if (!Number.isFinite(nonce)) nonce = 0;
222
- const gasLimit = String(gl);
223
- if (legacy) {
224
- const gp = p.txGasPrice ?? p.txgasprice;
225
- return {
226
- nonce,
227
- gasLimit,
228
- txType: "legacy",
229
- gasPrice: gp != null ? String(gp) : "0"
230
- };
231
- }
232
- return {
233
- nonce,
234
- gasLimit,
235
- txType: "eip1559",
236
- maxFeePerGas: String(p.txMaxFeePerGas ?? ""),
237
- maxPriorityFeePerGas: String(p.txMaxPriorityFeePerGas ?? "")
238
- };
239
- }
240
- function triggerTxParamsFromComposeBody(body) {
241
- const existing = body.txParams;
242
- if (existing && typeof existing === "object" && !Array.isArray(existing)) {
243
- const o = existing;
244
- if (String(o.gasLimit ?? "").trim() !== "") {
245
- return { ...o };
246
- }
247
- }
248
- const pb = body.proposalTxParams;
249
- if (Array.isArray(pb) && pb.length > 0 && typeof pb[0] === "object" && pb[0] !== null) {
250
- const first = pb[0];
251
- if (String(first.gasLimit ?? "").trim() !== "") {
252
- return { ...first };
253
- }
254
- }
255
- const fromSnapshot = composeFeePayloadToTxParams(
256
- body,
257
- body.txMaxFeePerGas == null && body.txMaxPriorityFeePerGas == null
258
- );
259
- if (fromSnapshot) return fromSnapshot;
260
- return {
261
- nonce: 0,
262
- gasLimit: "",
263
- txType: "legacy",
264
- gasPrice: "0"
265
- };
266
- }
267
- function proposalTxParamsToFeeSnapshot(params) {
268
- if (params.txType === "legacy") {
269
- return {
270
- txNonce: params.nonce,
271
- txGasLimit: params.gasLimit,
272
- txGasPrice: params.gasPrice ?? "0"
273
- };
274
- }
275
- return {
276
- txNonce: params.nonce,
277
- txGasLimit: params.gasLimit,
278
- txMaxFeePerGas: params.maxFeePerGas ?? "",
279
- txMaxPriorityFeePerGas: params.maxPriorityFeePerGas ?? ""
280
- };
281
- }
282
- async function fetchChainFeeParams(rpcUrl, chainId) {
283
- const url = rpcUrl.trim();
284
- if (!url) return { isEip1559: false };
285
- const chainIdNum = typeof chainId === "string" ? parseInt(chainId, 10) : chainId;
286
- if (Number.isNaN(chainIdNum)) return { isEip1559: false };
287
- const chain = defineChain({
288
- id: chainIdNum,
289
- name: "Discovery",
290
- nativeCurrency: { decimals: 18, name: "Ether", symbol: "ETH" },
291
- rpcUrls: { default: { http: [url] } }
292
- });
293
- const publicClient = createPublicClient({
294
- chain,
295
- transport: http(url)
296
- });
297
- const getGasPriceGwei = async () => {
298
- const gasPriceWei = await publicClient.getGasPrice();
299
- return parseFloat(formatUnits(gasPriceWei, 9));
300
- };
301
- try {
302
- const block = await publicClient.getBlock({ blockTag: "latest" });
303
- const baseFeePerGas = block?.baseFeePerGas;
304
- if (baseFeePerGas == null || baseFeePerGas === void 0) {
305
- const gasPriceGwei2 = await getGasPriceGwei();
306
- return { isEip1559: false, gasPriceGwei: gasPriceGwei2 };
307
- }
308
- const baseFeeGwei = parseFloat(formatUnits(baseFeePerGas, 9));
309
- let priorityFeeGwei;
310
- try {
311
- const priorityWei = await publicClient.estimateMaxPriorityFeePerGas();
312
- priorityFeeGwei = parseFloat(formatUnits(priorityWei, 9));
313
- } catch {
314
- }
315
- const gasPriceGwei = await getGasPriceGwei();
316
- return {
317
- isEip1559: true,
318
- baseFeeGwei,
319
- priorityFeeGwei,
320
- gasPriceGwei
321
- };
322
- } catch {
323
- try {
324
- const gasPriceWei = await publicClient.getGasPrice();
325
- const gasPriceGwei = parseFloat(formatUnits(gasPriceWei, 9));
326
- return { isEip1559: false, gasPriceGwei };
327
- } catch {
328
- return { isEip1559: false };
329
- }
330
- }
331
- }
332
- function finalizeEip1559Fees(maxFeePerGas, maxPriorityFeePerGas, floor, baseWei) {
333
- let maxP = maxPriorityFeePerGas;
334
- let maxF = maxFeePerGas;
335
- if (baseWei > 0n && maxF < baseWei + maxP) {
336
- maxF = baseWei + maxP + parseGwei("0.001");
337
- }
338
- if (maxF < maxP) {
339
- maxF = baseWei > 0n ? baseWei + maxP + parseGwei("0.001") : maxP * 2n;
340
- }
341
- return { maxFeePerGas: maxF, maxPriorityFeePerGas: maxP };
342
- }
343
- function alignEip1559FeesWithLatestBase(maxFeePerGas, maxPriorityFeePerGas, latestBlockBaseFeeWei) {
344
- return finalizeEip1559Fees(maxFeePerGas, maxPriorityFeePerGas, null, latestBlockBaseFeeWei);
345
- }
346
- function gweiToDecimalString(n) {
347
- if (!Number.isFinite(n)) return "0";
348
- if (n === 0) return "0";
349
- const s = String(n);
350
- if (s.indexOf("e") !== -1 || s.indexOf("E") !== -1) return n.toFixed(9).replace(/\.?0+$/, "") || "0";
351
- return s;
352
- }
353
109
 
354
110
  // src/chains/evm/buildBatch.ts
355
111
  async function buildEvmMultisignBatch(args) {
@@ -385,15 +141,19 @@ async function buildEvmMultisignBatch(args) {
385
141
  const step = steps[i];
386
142
  const currentNonce = baseNonce + i;
387
143
  let estimatedGas;
388
- try {
389
- estimatedGas = await publicClient.estimateGas({
390
- to: step.to,
391
- data: step.data,
392
- value: step.value,
393
- account: executor
394
- });
395
- } catch {
396
- estimatedGas = step.fallbackGas ?? 100000n;
144
+ if (args.estimateGasForStep) {
145
+ estimatedGas = await args.estimateGasForStep({ step, index: i, publicClient, executor });
146
+ } else {
147
+ try {
148
+ estimatedGas = await publicClient.estimateGas({
149
+ to: step.to,
150
+ data: step.data,
151
+ value: step.value,
152
+ account: executor
153
+ });
154
+ } catch {
155
+ estimatedGas = step.fallbackGas ?? 100000n;
156
+ }
397
157
  }
398
158
  let gasLimitI;
399
159
  if (args.resolveGasLimit) {
@@ -515,30 +275,6 @@ var evmChainCategoryModule = {
515
275
  buildEvmMultisignBatch
516
276
  };
517
277
 
518
- // src/chains/evm/fees/customGas.ts
519
- function chainSnapshotForCustomGasExtraJSON(chainDetail) {
520
- const lr = chainDetail.legacy;
521
- const legacy = lr === true || typeof lr === "string" && lr.toLowerCase() === "true";
522
- const push = (o, key, v) => {
523
- if (v === void 0 || v === null) return;
524
- if (typeof v === "string" && v.trim() === "") return;
525
- o[key] = v;
526
- };
527
- const fields = {};
528
- push(fields, "gasLimit", chainDetail.gasLimit);
529
- if (legacy) {
530
- push(fields, "gasMultiplier", chainDetail.gasMultiplier);
531
- push(fields, "gasPrice", chainDetail.gasPrice);
532
- } else {
533
- push(fields, "gasMultiplier", chainDetail.gasMultiplier);
534
- push(fields, "baseFee", chainDetail.baseFee);
535
- push(fields, "priorityFee", chainDetail.priorityFee);
536
- push(fields, "baseFeeMultiplier", chainDetail.baseFeeMultiplier);
537
- }
538
- push(fields, "legacy", legacy);
539
- return fields;
540
- }
541
-
542
278
  // src/chains/evm/chainIdParse.ts
543
279
  function parseEvmChainIdToNumber(chainId) {
544
280
  if (chainId == null) return Number.NaN;
@@ -986,6 +722,35 @@ async function uniswapCreateSwap(args) {
986
722
  }
987
723
  return parsed;
988
724
  }
725
+ async function estimateUniswapRouterSwapGas(args) {
726
+ const fromTradeApi = parseOptionalGasLimitString(args.swapRecord.gasLimit) ?? parseOptionalGasLimitString(args.swapRecord.gas);
727
+ if (fromTradeApi != null && fromTradeApi > 0n) {
728
+ return { baseGasUnits: fromTradeApi, source: "tradeApi" };
729
+ }
730
+ try {
731
+ const est = await args.publicClient.estimateGas({
732
+ to: args.to,
733
+ data: args.data,
734
+ value: args.value,
735
+ account: args.executor
736
+ });
737
+ return { baseGasUnits: est, source: "rpcEstimate" };
738
+ } catch (e) {
739
+ const estimateGasError = e instanceof Error ? e.message : String(e);
740
+ const minRouterGas = 500000n;
741
+ if (args.useCustomGas) {
742
+ const cfg = args.chainDetail?.gasLimit != null ? parseOptionalGasLimitString(String(args.chainDetail.gasLimit)) : null;
743
+ if (cfg != null && cfg >= minRouterGas) {
744
+ return { baseGasUnits: cfg, source: "estimateFailedFallback", estimateGasError };
745
+ }
746
+ }
747
+ return {
748
+ baseGasUnits: DEFAULT_UNIVERSAL_ROUTER_GAS_FALLBACK,
749
+ source: "estimateFailedFallback",
750
+ estimateGasError
751
+ };
752
+ }
753
+ }
989
754
  var permit2ApproveRouterAbi = [
990
755
  {
991
756
  name: "approve",
@@ -1086,10 +851,6 @@ function permit2SpenderAndApproveWeiFromSwapCalldata(dataHex, tokenIn, quoteInpu
1086
851
  }
1087
852
  }
1088
853
  async function buildEvmMultisignBodyUniswapV4NativeInOnly(args, quoteInputWei) {
1089
- const ph = (args.keyGen.pubkeyhex ?? "").trim();
1090
- if (!ph) throw new Error("keyGen pubKey (pubkeyhex) is required");
1091
- const keyList = args.keyGen.keylist ?? [];
1092
- const clientId = firstClientIdFromKeyGen(args.keyGen);
1093
854
  const toRouter = getAddress(
1094
855
  (args.swap.to ?? "").trim().startsWith("0x") ? args.swap.to.trim() : `0x${args.swap.to.trim()}`
1095
856
  );
@@ -1107,214 +868,90 @@ async function buildEvmMultisignBodyUniswapV4NativeInOnly(args, quoteInputWei) {
1107
868
  "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."
1108
869
  );
1109
870
  }
1110
- const ch = defineChain({
1111
- id: args.chainId,
1112
- name: "Destination",
1113
- nativeCurrency: { decimals: 18, name: "Ether", symbol: "ETH" },
1114
- rpcUrls: { default: { http: [args.rpcUrl] } }
1115
- });
1116
- const publicClient = createPublicClient({ chain: ch, transport: http(args.rpcUrl) });
1117
- const feeParams = await fetchChainFeeParams(args.rpcUrl, args.chainId);
1118
- const legacy = Boolean(args.chainDetail?.legacy) || !feeParams.isEip1559;
1119
- const latestBaseFeeWeiNativeIn = !legacy ? (await publicClient.getBlock({ blockTag: "latest" })).baseFeePerGas ?? 0n : 0n;
1120
- const useCustomGas = args.useCustomGas;
1121
- const chainGasLimitRouter = args.chainDetail?.gasLimit != null && Number.isFinite(Number(args.chainDetail.gasLimit)) && Number(args.chainDetail.gasLimit) > 0 ? Number(args.chainDetail.gasLimit) : void 0;
1122
- const gasFeeMultiplier = useCustomGas && args.chainDetail?.gasMultiplier != null ? Number(args.chainDetail.gasMultiplier) : void 0;
1123
- const nonce = await publicClient.getTransactionCount({ address: args.executorAddress, blockTag: "pending" });
1124
- const proposalTxParamsBatch = [];
1125
- const messageHashes = [];
1126
- const messageRawBatch = [];
1127
871
  const swapRecord = args.swap;
1128
- const fromTradeApi = parseOptionalGasLimitString(swapRecord.gasLimit) ?? parseOptionalGasLimitString(swapRecord.gas);
1129
872
  let gasBuildSource = "rpcEstimate";
1130
873
  let estimateGasError;
1131
- let baseGasUnits1;
1132
- if (fromTradeApi != null && fromTradeApi > 0n) {
1133
- baseGasUnits1 = fromTradeApi;
1134
- gasBuildSource = "tradeApi";
1135
- } else {
1136
- try {
1137
- baseGasUnits1 = await publicClient.estimateGas({
874
+ let swapBaseGasUnits = DEFAULT_UNIVERSAL_ROUTER_GAS_FALLBACK;
875
+ const dataNo0x = dataHex.startsWith("0x") ? dataHex.slice(2) : dataHex;
876
+ const purposeSuffix = "Uniswap V4: 1-tx batch \u2014 native gas token in (no ERC-20 approve) \u2014 single payable swap (Trade /swap).";
877
+ return buildEvmMultisignBatch({
878
+ context: {
879
+ chainCategory: "evm",
880
+ keyGen: args.keyGen,
881
+ purposeText: args.purposeText,
882
+ chainId: args.chainId,
883
+ rpcUrl: args.rpcUrl,
884
+ executorAddress: args.executorAddress,
885
+ chainDetail: args.chainDetail,
886
+ useCustomGas: args.useCustomGas,
887
+ customGasChainDetails: args.customGasChainDetails
888
+ },
889
+ steps: [
890
+ {
1138
891
  to: toRouter,
1139
892
  data: dataHex,
1140
893
  value: valueWei,
1141
- account: args.executorAddress
1142
- });
1143
- gasBuildSource = "rpcEstimate";
1144
- } catch (e) {
1145
- estimateGasError = e instanceof Error ? e.message : String(e);
1146
- const minRouterGas = 500000n;
1147
- if (useCustomGas) {
1148
- const cfg = args.chainDetail?.gasLimit != null ? parseOptionalGasLimitString(String(args.chainDetail.gasLimit)) : null;
1149
- if (cfg != null && cfg >= minRouterGas) {
1150
- baseGasUnits1 = cfg;
1151
- } else {
1152
- baseGasUnits1 = DEFAULT_UNIVERSAL_ROUTER_GAS_FALLBACK;
1153
- }
1154
- } else {
1155
- baseGasUnits1 = DEFAULT_UNIVERSAL_ROUTER_GAS_FALLBACK;
1156
- }
1157
- gasBuildSource = "estimateFailedFallback";
1158
- }
1159
- }
1160
- const gasLimit1 = routerSwapGasLimitFromEstimate(baseGasUnits1, chainGasLimitRouter);
1161
- const currentNonce0 = nonce;
1162
- let firstTxFeePayload = {};
1163
- if (legacy) {
1164
- let gasPriceWei1 = await publicClient.getGasPrice();
1165
- if (useCustomGas && gasFeeMultiplier != null && gasFeeMultiplier > 0) {
1166
- gasPriceWei1 = gasPriceWei1 * BigInt(100 + gasFeeMultiplier) / 100n;
1167
- }
1168
- if (useCustomGas && args.chainDetail?.gasPrice != null && args.chainDetail.gasPrice > 0) {
1169
- const configured = parseGwei(gweiToDecimalString(Number(args.chainDetail.gasPrice)));
1170
- if (configured > gasPriceWei1) gasPriceWei1 = configured;
1171
- }
1172
- firstTxFeePayload = { txNonce: nonce, txGasLimit: gasLimit1.toString(), txGasPrice: gasPriceWei1.toString() };
1173
- const ser0 = serializeTransaction({
1174
- type: "legacy",
1175
- to: toRouter,
1176
- data: dataHex,
1177
- value: valueWei,
1178
- gas: gasLimit1,
1179
- gasPrice: gasPriceWei1,
1180
- nonce: currentNonce0,
1181
- chainId: args.chainId
1182
- });
1183
- const h0 = keccak256(ser0);
1184
- messageHashes.push(h0.startsWith("0x") ? h0.slice(2) : h0);
1185
- messageRawBatch.push(ser0);
1186
- proposalTxParamsBatch.push({
1187
- nonce: currentNonce0,
1188
- gasLimit: gasLimit1.toString(),
1189
- txType: "legacy",
1190
- gasPrice: gasPriceWei1.toString()
1191
- });
1192
- } else {
1193
- const fetchedBase1 = feeParams.baseFeeGwei ?? 0;
1194
- const fetchedPriority1 = feeParams.priorityFeeGwei ?? 0;
1195
- const configuredBase1 = useCustomGas && args.chainDetail?.baseFee != null ? Number(args.chainDetail.baseFee) : 0;
1196
- const configuredPriority1 = useCustomGas && args.chainDetail?.priorityFee != null ? Number(args.chainDetail.priorityFee) : 0;
1197
- const effectiveBaseFeeGwei1 = Math.max(fetchedBase1, configuredBase1);
1198
- const effectivePriorityFeeGwei1 = Math.max(fetchedPriority1, configuredPriority1);
1199
- const baseFeeMultiplierPct1 = useCustomGas && args.chainDetail?.baseFeeMultiplier != null ? Math.max(100, Number(args.chainDetail.baseFeeMultiplier)) : 100;
1200
- const baseComponentGwei1 = effectiveBaseFeeGwei1 * baseFeeMultiplierPct1 / 100;
1201
- const maxFeePerGasGwei1 = baseComponentGwei1 + effectivePriorityFeeGwei1;
1202
- let maxPriorityFeePerGas1 = effectivePriorityFeeGwei1 > 0 ? parseGwei(gweiToDecimalString(effectivePriorityFeeGwei1)) : parseGwei("1");
1203
- let maxFeePerGas1 = parseGwei(gweiToDecimalString(maxFeePerGasGwei1));
1204
- if (useCustomGas && gasFeeMultiplier != null && gasFeeMultiplier > 0) {
1205
- maxPriorityFeePerGas1 = maxPriorityFeePerGas1 * BigInt(100 + gasFeeMultiplier) / 100n;
1206
- maxFeePerGas1 = maxFeePerGas1 * BigInt(100 + gasFeeMultiplier) / 100n;
1207
- }
1208
- ({ maxFeePerGas: maxFeePerGas1, maxPriorityFeePerGas: maxPriorityFeePerGas1 } = alignEip1559FeesWithLatestBase(maxFeePerGas1, maxPriorityFeePerGas1, latestBaseFeeWeiNativeIn));
1209
- firstTxFeePayload = {
1210
- txNonce: nonce,
1211
- txGasLimit: gasLimit1.toString(),
1212
- txMaxFeePerGas: maxFeePerGas1.toString(),
1213
- txMaxPriorityFeePerGas: maxPriorityFeePerGas1.toString()
1214
- };
1215
- const ser0 = serializeTransaction({
1216
- type: "eip1559",
1217
- to: toRouter,
1218
- data: dataHex,
1219
- value: valueWei,
1220
- gas: gasLimit1,
1221
- maxFeePerGas: maxFeePerGas1,
1222
- maxPriorityFeePerGas: maxPriorityFeePerGas1,
1223
- nonce: currentNonce0,
1224
- chainId: args.chainId
1225
- });
1226
- const h0 = keccak256(ser0);
1227
- messageHashes.push(h0.startsWith("0x") ? h0.slice(2) : h0);
1228
- messageRawBatch.push(ser0);
1229
- proposalTxParamsBatch.push({
1230
- nonce: currentNonce0,
1231
- gasLimit: gasLimit1.toString(),
1232
- txType: "eip1559",
1233
- maxFeePerGas: maxFeePerGas1.toString(),
1234
- maxPriorityFeePerGas: maxPriorityFeePerGas1.toString()
1235
- });
1236
- }
1237
- const dataNo0x = dataHex.startsWith("0x") ? dataHex.slice(2) : dataHex;
1238
- const audit = {
1239
- skipPermit2Batch: true,
1240
- inputKind: "native_eth",
1241
- noErc20Approve: true,
1242
- quoteInputWei: quoteInputWei.toString(),
1243
- swapValueWei: valueWei.toString(),
1244
- uniswapCreateSwap: {
1245
- requestId: args.createSwapResponse.requestId,
1246
- gasFee: args.createSwapResponse.gasFee,
1247
- gasBuildSwap: {
1248
- useCustomGas,
1249
- source: gasBuildSource,
1250
- baseGasUnits: baseGasUnits1.toString(),
1251
- ...estimateGasError != null && estimateGasError !== "" ? { estimateGasError } : {}
1252
- },
1253
- swap: {
1254
- to: args.createSwapResponse.swap.to,
1255
- value: args.createSwapResponse.swap.value,
1256
- dataNibbles: (() => {
1257
- const t = (args.createSwapResponse.swap.data ?? "").toString().trim();
1258
- return t.startsWith("0x") ? t.length - 2 : t.length;
1259
- })()
894
+ routerSwap: true,
895
+ fallbackGas: DEFAULT_UNIVERSAL_ROUTER_GAS_FALLBACK
1260
896
  }
897
+ ],
898
+ purposeSuffix,
899
+ firstMsgRawNo0x: dataNo0x,
900
+ destinationAddress: toRouter,
901
+ estimateGasForStep: async ({ publicClient, executor }) => {
902
+ const r = await estimateUniswapRouterSwapGas({
903
+ publicClient,
904
+ executor,
905
+ to: toRouter,
906
+ data: dataHex,
907
+ value: valueWei,
908
+ swapRecord,
909
+ useCustomGas: args.useCustomGas,
910
+ chainDetail: args.chainDetail
911
+ });
912
+ gasBuildSource = r.source;
913
+ estimateGasError = r.estimateGasError;
914
+ swapBaseGasUnits = r.baseGasUnits;
915
+ return r.baseGasUnits;
1261
916
  },
1262
- fullQuoteFromPermitSnapshot: args.fullQuoteSnapshot,
1263
- originalPurpose: args.purposeText
1264
- };
1265
- const batchMeta = [
1266
- {
1267
- destinationAddress: toRouter,
917
+ buildBatchMeta: () => ({
1268
918
  signatureText: JSON.stringify({
1269
919
  kind: "UniswapV4",
1270
920
  name: "UniversalRouter (payable, native in)",
1271
921
  note: "Single tx from Trade POST /swap; no ERC-20 approve. Calldata in messageHashes[0] / messageRawBatch[0]."
1272
922
  }),
1273
923
  evm: { type: "uniswap_v4_swap_tx", version: 1, chainId: String(args.chainId) },
1274
- uniswapV4: audit
1275
- }
1276
- ];
1277
- const extraPayload = { batchMeta };
1278
- if (useCustomGas) {
1279
- const snap = args.customGasChainDetails;
1280
- if (snap && typeof snap === "object" && !Array.isArray(snap) && Object.keys(snap).length > 0) {
1281
- extraPayload.customGasChainDetails = snap;
1282
- }
1283
- }
1284
- const extraJSON = JSON.stringify(extraPayload);
1285
- const firstSigText = batchMeta[0].signatureText;
1286
- const bodyForSign = {
1287
- keyList,
1288
- pubKey: ph,
1289
- msgHash: messageHashes[0],
1290
- msgRaw: dataNo0x,
1291
- messageHashes,
1292
- messageRawBatch,
1293
- destinationChainID: String(args.chainId),
1294
- destinationAddress: toRouter,
1295
- extraJSON,
1296
- signatureText: firstSigText,
1297
- purpose: (() => {
1298
- const t = args.purposeText.trim();
1299
- const batchDesc = "Uniswap V4: 1-tx batch \u2014 native gas token in (no ERC-20 approve) \u2014 single payable swap (Trade /swap).";
1300
- return (t ? `${t}
1301
-
1302
- ` : "") + batchDesc;
1303
- })(),
1304
- ...firstTxFeePayload,
1305
- proposalTxParams: proposalTxParamsBatch
1306
- };
1307
- if (valueWei > 0n) {
1308
- bodyForSign.value = valueWei.toString();
1309
- }
1310
- if (clientId) bodyForSign.clientId = clientId;
1311
- return { bodyForSign, messageToSign: JSON.stringify(bodyForSign) };
924
+ uniswapV4: {
925
+ skipPermit2Batch: true,
926
+ inputKind: "native_eth",
927
+ noErc20Approve: true,
928
+ quoteInputWei: quoteInputWei.toString(),
929
+ swapValueWei: valueWei.toString(),
930
+ uniswapCreateSwap: {
931
+ requestId: args.createSwapResponse.requestId,
932
+ gasFee: args.createSwapResponse.gasFee,
933
+ gasBuildSwap: {
934
+ useCustomGas: args.useCustomGas,
935
+ source: gasBuildSource,
936
+ baseGasUnits: swapBaseGasUnits.toString(),
937
+ ...estimateGasError != null && estimateGasError !== "" ? { estimateGasError } : {}
938
+ },
939
+ swap: {
940
+ to: args.createSwapResponse.swap.to,
941
+ value: args.createSwapResponse.swap.value,
942
+ dataNibbles: (() => {
943
+ const t = (args.createSwapResponse.swap.data ?? "").toString().trim();
944
+ return t.startsWith("0x") ? t.length - 2 : t.length;
945
+ })()
946
+ }
947
+ },
948
+ fullQuoteFromPermitSnapshot: args.fullQuoteSnapshot,
949
+ originalPurpose: args.purposeText
950
+ }
951
+ })
952
+ });
1312
953
  }
1313
954
  async function buildEvmMultisignBodyUniswapV4SkipPermit2Batch(args) {
1314
- const ph = (args.keyGen.pubkeyhex ?? "").trim();
1315
- if (!ph) throw new Error("keyGen pubKey (pubkeyhex) is required");
1316
- const keyList = args.keyGen.keylist ?? [];
1317
- const clientId = firstClientIdFromKeyGen(args.keyGen);
1318
955
  const tokenIn = getAddress(args.tokenIn);
1319
956
  const parsedInOut = parseUniswapQuoteClassicInOut(args.fullQuoteSnapshot);
1320
957
  const quoteInputWei = parsedInOut?.inputWei;
@@ -1369,298 +1006,33 @@ async function buildEvmMultisignBodyUniswapV4SkipPermit2Batch(args) {
1369
1006
  return 0n;
1370
1007
  }
1371
1008
  })();
1372
- const ch = defineChain({
1373
- id: args.chainId,
1374
- name: "Destination",
1375
- nativeCurrency: { decimals: 18, name: "Ether", symbol: "ETH" },
1376
- rpcUrls: { default: { http: [args.rpcUrl] } }
1377
- });
1378
- const publicClient = createPublicClient({ chain: ch, transport: http(args.rpcUrl) });
1379
- const feeParams = await fetchChainFeeParams(args.rpcUrl, args.chainId);
1380
- const legacy = Boolean(args.chainDetail?.legacy) || !feeParams.isEip1559;
1381
- const latestBaseFeeWeiSkipBatch = !legacy ? (await publicClient.getBlock({ blockTag: "latest" })).baseFeePerGas ?? 0n : 0n;
1382
- const useCustomGas = args.useCustomGas;
1383
- const gasLimitConfig = useCustomGas && args.chainDetail?.gasLimit != null ? Number(args.chainDetail.gasLimit) : void 0;
1384
- const chainGasLimitRouter = args.chainDetail?.gasLimit != null && Number.isFinite(Number(args.chainDetail.gasLimit)) && Number(args.chainDetail.gasLimit) > 0 ? Number(args.chainDetail.gasLimit) : void 0;
1385
- const gasFeeMultiplier = useCustomGas && args.chainDetail?.gasMultiplier != null ? Number(args.chainDetail.gasMultiplier) : void 0;
1386
- const nonce = await publicClient.getTransactionCount({ address: args.executorAddress, blockTag: "pending" });
1387
- const proposalTxParamsBatch = [];
1388
- const messageHashes = [];
1389
- const messageRawBatch = [];
1390
- const approveMsgRawNo0x = approveData.startsWith("0x") ? approveData.slice(2) : approveData;
1391
- let firstTxFeePayload = {};
1392
- const approveGas = await publicClient.estimateGas({
1393
- to: tokenIn,
1394
- data: approveData,
1395
- value: 0n,
1396
- account: args.executorAddress
1397
- });
1398
- const gasLimit0 = useCustomGas ? gasLimitFromEstimateAndChainConfig(approveGas, gasLimitConfig) : approveGas;
1399
- const currentNonce0 = nonce;
1400
- if (legacy) {
1401
- let gasPriceWei = await publicClient.getGasPrice();
1402
- if (useCustomGas && gasFeeMultiplier != null && gasFeeMultiplier > 0) {
1403
- gasPriceWei = gasPriceWei * BigInt(100 + gasFeeMultiplier) / 100n;
1404
- }
1405
- if (useCustomGas && args.chainDetail?.gasPrice != null && args.chainDetail.gasPrice > 0) {
1406
- const configured = parseGwei(gweiToDecimalString(Number(args.chainDetail.gasPrice)));
1407
- if (configured > gasPriceWei) gasPriceWei = configured;
1408
- }
1409
- firstTxFeePayload = { txNonce: nonce, txGasLimit: gasLimit0.toString(), txGasPrice: gasPriceWei.toString() };
1410
- const ser0 = serializeTransaction({
1411
- type: "legacy",
1412
- to: tokenIn,
1413
- data: approveData,
1414
- value: 0n,
1415
- gas: gasLimit0,
1416
- gasPrice: gasPriceWei,
1417
- nonce: currentNonce0,
1418
- chainId: args.chainId
1419
- });
1420
- const h0 = keccak256(ser0);
1421
- messageHashes.push(h0.startsWith("0x") ? h0.slice(2) : h0);
1422
- messageRawBatch.push(ser0);
1423
- proposalTxParamsBatch.push({
1424
- nonce: currentNonce0,
1425
- gasLimit: gasLimit0.toString(),
1426
- txType: "legacy",
1427
- gasPrice: gasPriceWei.toString()
1428
- });
1429
- } else {
1430
- const fetchedBase = feeParams.baseFeeGwei ?? 0;
1431
- const fetchedPriority = feeParams.priorityFeeGwei ?? 0;
1432
- const configuredBase = useCustomGas && args.chainDetail?.baseFee != null ? Number(args.chainDetail.baseFee) : 0;
1433
- const configuredPriority = useCustomGas && args.chainDetail?.priorityFee != null ? Number(args.chainDetail.priorityFee) : 0;
1434
- const effectiveBaseFeeGwei = Math.max(fetchedBase, configuredBase);
1435
- const effectivePriorityFeeGwei = Math.max(fetchedPriority, configuredPriority);
1436
- const baseFeeMultiplierPct = useCustomGas && args.chainDetail?.baseFeeMultiplier != null ? Math.max(100, Number(args.chainDetail.baseFeeMultiplier)) : 100;
1437
- const baseComponentGwei = effectiveBaseFeeGwei * baseFeeMultiplierPct / 100;
1438
- const maxFeePerGasGwei = baseComponentGwei + effectivePriorityFeeGwei;
1439
- let maxPriorityFeePerGas = effectivePriorityFeeGwei > 0 ? parseGwei(gweiToDecimalString(effectivePriorityFeeGwei)) : parseGwei("1");
1440
- let maxFeePerGas = parseGwei(gweiToDecimalString(maxFeePerGasGwei));
1441
- if (useCustomGas && gasFeeMultiplier != null && gasFeeMultiplier > 0) {
1442
- maxPriorityFeePerGas = maxPriorityFeePerGas * BigInt(100 + gasFeeMultiplier) / 100n;
1443
- maxFeePerGas = maxFeePerGas * BigInt(100 + gasFeeMultiplier) / 100n;
1444
- }
1445
- ({ maxFeePerGas, maxPriorityFeePerGas } = alignEip1559FeesWithLatestBase(
1446
- maxFeePerGas,
1447
- maxPriorityFeePerGas,
1448
- latestBaseFeeWeiSkipBatch
1449
- ));
1450
- firstTxFeePayload = {
1451
- txNonce: nonce,
1452
- txGasLimit: gasLimit0.toString(),
1453
- txMaxFeePerGas: maxFeePerGas.toString(),
1454
- txMaxPriorityFeePerGas: maxPriorityFeePerGas.toString()
1455
- };
1456
- const ser0 = serializeTransaction({
1457
- type: "eip1559",
1458
- to: tokenIn,
1459
- data: approveData,
1460
- value: 0n,
1461
- gas: gasLimit0,
1462
- maxFeePerGas,
1463
- maxPriorityFeePerGas,
1464
- nonce: currentNonce0,
1465
- chainId: args.chainId
1466
- });
1467
- const h0 = keccak256(ser0);
1468
- messageHashes.push(h0.startsWith("0x") ? h0.slice(2) : h0);
1469
- messageRawBatch.push(ser0);
1470
- proposalTxParamsBatch.push({
1471
- nonce: currentNonce0,
1472
- gasLimit: gasLimit0.toString(),
1473
- txType: "eip1559",
1474
- maxFeePerGas: maxFeePerGas.toString(),
1475
- maxPriorityFeePerGas: maxPriorityFeePerGas.toString()
1476
- });
1477
- }
1009
+ const swapRecord = args.swap;
1010
+ const swapMsgIndex = usePermit2Triple ? 2 : 1;
1011
+ let gasBuildSource = "rpcEstimate";
1012
+ let estimateGasError;
1013
+ let swapBaseGasUnits = DEFAULT_UNIVERSAL_ROUTER_GAS_FALLBACK;
1014
+ const steps = [
1015
+ { to: tokenIn, data: approveData, value: 0n, fallbackGas: 100000n }
1016
+ ];
1478
1017
  if (usePermit2Triple) {
1479
1018
  const permit2ApproveData = encodeFunctionData({
1480
1019
  abi: permit2ApproveRouterAbi,
1481
1020
  functionName: "approve",
1482
1021
  args: [tokenIn, permit2Spender, approveAmountWei, Number(expiration48)]
1483
1022
  });
1484
- const approveP2Gas = await publicClient.estimateGas({
1485
- to: PERMIT2_ADDRESS,
1486
- data: permit2ApproveData,
1487
- value: 0n,
1488
- account: args.executorAddress
1489
- });
1490
- const gasLimitP2 = useCustomGas ? gasLimitFromEstimateAndChainConfig(approveP2Gas, gasLimitConfig) : approveP2Gas;
1491
- const currentNonceP2 = nonce + 1;
1492
- if (legacy) {
1493
- let gasPriceWeiP2 = await publicClient.getGasPrice();
1494
- if (useCustomGas && gasFeeMultiplier != null && gasFeeMultiplier > 0) {
1495
- gasPriceWeiP2 = gasPriceWeiP2 * BigInt(100 + gasFeeMultiplier) / 100n;
1496
- }
1497
- if (useCustomGas && args.chainDetail?.gasPrice != null && args.chainDetail.gasPrice > 0) {
1498
- const configured = parseGwei(gweiToDecimalString(Number(args.chainDetail.gasPrice)));
1499
- if (configured > gasPriceWeiP2) gasPriceWeiP2 = configured;
1500
- }
1501
- const serP2 = serializeTransaction({
1502
- type: "legacy",
1503
- to: PERMIT2_ADDRESS,
1504
- data: permit2ApproveData,
1505
- value: 0n,
1506
- gas: gasLimitP2,
1507
- gasPrice: gasPriceWeiP2,
1508
- nonce: currentNonceP2,
1509
- chainId: args.chainId
1510
- });
1511
- const hP2 = keccak256(serP2);
1512
- messageHashes.push(hP2.startsWith("0x") ? hP2.slice(2) : hP2);
1513
- messageRawBatch.push(serP2);
1514
- proposalTxParamsBatch.push({
1515
- nonce: currentNonceP2,
1516
- gasLimit: gasLimitP2.toString(),
1517
- txType: "legacy",
1518
- gasPrice: gasPriceWeiP2.toString()
1519
- });
1520
- } else {
1521
- const fetchedBaseP2 = feeParams.baseFeeGwei ?? 0;
1522
- const fetchedPriorityP2 = feeParams.priorityFeeGwei ?? 0;
1523
- const configuredBaseP2 = useCustomGas && args.chainDetail?.baseFee != null ? Number(args.chainDetail.baseFee) : 0;
1524
- const configuredPriorityP2 = useCustomGas && args.chainDetail?.priorityFee != null ? Number(args.chainDetail.priorityFee) : 0;
1525
- const effectiveBaseFeeGweiP2 = Math.max(fetchedBaseP2, configuredBaseP2);
1526
- const effectivePriorityFeeGweiP2 = Math.max(fetchedPriorityP2, configuredPriorityP2);
1527
- const baseFeeMultiplierPctP2 = useCustomGas && args.chainDetail?.baseFeeMultiplier != null ? Math.max(100, Number(args.chainDetail.baseFeeMultiplier)) : 100;
1528
- const baseComponentGweiP2 = effectiveBaseFeeGweiP2 * baseFeeMultiplierPctP2 / 100;
1529
- const maxFeePerGasGweiP2 = baseComponentGweiP2 + effectivePriorityFeeGweiP2;
1530
- let maxPriorityFeePerGasP2 = effectivePriorityFeeGweiP2 > 0 ? parseGwei(gweiToDecimalString(effectivePriorityFeeGweiP2)) : parseGwei("1");
1531
- let maxFeePerGasP2 = parseGwei(gweiToDecimalString(maxFeePerGasGweiP2));
1532
- if (useCustomGas && gasFeeMultiplier != null && gasFeeMultiplier > 0) {
1533
- maxPriorityFeePerGasP2 = maxPriorityFeePerGasP2 * BigInt(100 + gasFeeMultiplier) / 100n;
1534
- maxFeePerGasP2 = maxFeePerGasP2 * BigInt(100 + gasFeeMultiplier) / 100n;
1535
- }
1536
- ({ maxFeePerGas: maxFeePerGasP2, maxPriorityFeePerGas: maxPriorityFeePerGasP2 } = alignEip1559FeesWithLatestBase(maxFeePerGasP2, maxPriorityFeePerGasP2, latestBaseFeeWeiSkipBatch));
1537
- const serP2 = serializeTransaction({
1538
- type: "eip1559",
1539
- to: PERMIT2_ADDRESS,
1540
- data: permit2ApproveData,
1541
- value: 0n,
1542
- gas: gasLimitP2,
1543
- maxFeePerGas: maxFeePerGasP2,
1544
- maxPriorityFeePerGas: maxPriorityFeePerGasP2,
1545
- nonce: currentNonceP2,
1546
- chainId: args.chainId
1547
- });
1548
- const hP2 = keccak256(serP2);
1549
- messageHashes.push(hP2.startsWith("0x") ? hP2.slice(2) : hP2);
1550
- messageRawBatch.push(serP2);
1551
- proposalTxParamsBatch.push({
1552
- nonce: currentNonceP2,
1553
- gasLimit: gasLimitP2.toString(),
1554
- txType: "eip1559",
1555
- maxFeePerGas: maxFeePerGasP2.toString(),
1556
- maxPriorityFeePerGas: maxPriorityFeePerGasP2.toString()
1557
- });
1558
- }
1023
+ steps.push({ to: PERMIT2_ADDRESS, data: permit2ApproveData, value: 0n, fallbackGas: 100000n });
1559
1024
  }
1560
- const swapRecord = args.swap;
1561
- const fromTradeApi = parseOptionalGasLimitString(swapRecord.gasLimit) ?? parseOptionalGasLimitString(swapRecord.gas);
1562
- let gasBuildSource = "rpcEstimate";
1563
- let estimateGasError;
1564
- let baseGasUnits1;
1565
- if (fromTradeApi != null && fromTradeApi > 0n) {
1566
- baseGasUnits1 = fromTradeApi;
1567
- gasBuildSource = "tradeApi";
1568
- } else {
1569
- try {
1570
- baseGasUnits1 = await publicClient.estimateGas({
1571
- to: toRouter,
1572
- data: dataHex,
1573
- value: valueWei,
1574
- account: args.executorAddress
1575
- });
1576
- gasBuildSource = "rpcEstimate";
1577
- } catch (e) {
1578
- estimateGasError = e instanceof Error ? e.message : String(e);
1579
- const minRouterGas = 500000n;
1580
- if (useCustomGas) {
1581
- const cfg = args.chainDetail?.gasLimit != null ? parseOptionalGasLimitString(String(args.chainDetail.gasLimit)) : null;
1582
- if (cfg != null && cfg >= minRouterGas) {
1583
- baseGasUnits1 = cfg;
1584
- } else {
1585
- baseGasUnits1 = DEFAULT_UNIVERSAL_ROUTER_GAS_FALLBACK;
1586
- }
1587
- } else {
1588
- baseGasUnits1 = DEFAULT_UNIVERSAL_ROUTER_GAS_FALLBACK;
1589
- }
1590
- gasBuildSource = "estimateFailedFallback";
1591
- }
1592
- }
1593
- const gasLimit1 = routerSwapGasLimitFromEstimate(baseGasUnits1, chainGasLimitRouter);
1594
- const currentNonce1 = nonce + (usePermit2Triple ? 2 : 1);
1595
- if (legacy) {
1596
- let gasPriceWei1 = await publicClient.getGasPrice();
1597
- if (useCustomGas && gasFeeMultiplier != null && gasFeeMultiplier > 0) {
1598
- gasPriceWei1 = gasPriceWei1 * BigInt(100 + gasFeeMultiplier) / 100n;
1599
- }
1600
- if (useCustomGas && args.chainDetail?.gasPrice != null && args.chainDetail.gasPrice > 0) {
1601
- const configured = parseGwei(gweiToDecimalString(Number(args.chainDetail.gasPrice)));
1602
- if (configured > gasPriceWei1) gasPriceWei1 = configured;
1603
- }
1604
- const ser1 = serializeTransaction({
1605
- type: "legacy",
1606
- to: toRouter,
1607
- data: dataHex,
1608
- value: valueWei,
1609
- gas: gasLimit1,
1610
- gasPrice: gasPriceWei1,
1611
- nonce: currentNonce1,
1612
- chainId: args.chainId
1613
- });
1614
- const h1 = keccak256(ser1);
1615
- messageHashes.push(h1.startsWith("0x") ? h1.slice(2) : h1);
1616
- messageRawBatch.push(ser1);
1617
- proposalTxParamsBatch.push({
1618
- nonce: currentNonce1,
1619
- gasLimit: gasLimit1.toString(),
1620
- txType: "legacy",
1621
- gasPrice: gasPriceWei1.toString()
1622
- });
1623
- } else {
1624
- const fetchedBase1 = feeParams.baseFeeGwei ?? 0;
1625
- const fetchedPriority1 = feeParams.priorityFeeGwei ?? 0;
1626
- const configuredBase1 = useCustomGas && args.chainDetail?.baseFee != null ? Number(args.chainDetail.baseFee) : 0;
1627
- const configuredPriority1 = useCustomGas && args.chainDetail?.priorityFee != null ? Number(args.chainDetail.priorityFee) : 0;
1628
- const effectiveBaseFeeGwei1 = Math.max(fetchedBase1, configuredBase1);
1629
- const effectivePriorityFeeGwei1 = Math.max(fetchedPriority1, configuredPriority1);
1630
- const baseFeeMultiplierPct1 = useCustomGas && args.chainDetail?.baseFeeMultiplier != null ? Math.max(100, Number(args.chainDetail.baseFeeMultiplier)) : 100;
1631
- const baseComponentGwei1 = effectiveBaseFeeGwei1 * baseFeeMultiplierPct1 / 100;
1632
- const maxFeePerGasGwei1 = baseComponentGwei1 + effectivePriorityFeeGwei1;
1633
- let maxPriorityFeePerGas1 = effectivePriorityFeeGwei1 > 0 ? parseGwei(gweiToDecimalString(effectivePriorityFeeGwei1)) : parseGwei("1");
1634
- let maxFeePerGas1 = parseGwei(gweiToDecimalString(maxFeePerGasGwei1));
1635
- if (useCustomGas && gasFeeMultiplier != null && gasFeeMultiplier > 0) {
1636
- maxPriorityFeePerGas1 = maxPriorityFeePerGas1 * BigInt(100 + gasFeeMultiplier) / 100n;
1637
- maxFeePerGas1 = maxFeePerGas1 * BigInt(100 + gasFeeMultiplier) / 100n;
1638
- }
1639
- ({ maxFeePerGas: maxFeePerGas1, maxPriorityFeePerGas: maxPriorityFeePerGas1 } = alignEip1559FeesWithLatestBase(maxFeePerGas1, maxPriorityFeePerGas1, latestBaseFeeWeiSkipBatch));
1640
- const ser1 = serializeTransaction({
1641
- type: "eip1559",
1642
- to: toRouter,
1643
- data: dataHex,
1644
- value: valueWei,
1645
- gas: gasLimit1,
1646
- maxFeePerGas: maxFeePerGas1,
1647
- maxPriorityFeePerGas: maxPriorityFeePerGas1,
1648
- nonce: currentNonce1,
1649
- chainId: args.chainId
1650
- });
1651
- const h1 = keccak256(ser1);
1652
- messageHashes.push(h1.startsWith("0x") ? h1.slice(2) : h1);
1653
- messageRawBatch.push(ser1);
1654
- proposalTxParamsBatch.push({
1655
- nonce: currentNonce1,
1656
- gasLimit: gasLimit1.toString(),
1657
- txType: "eip1559",
1658
- maxFeePerGas: maxFeePerGas1.toString(),
1659
- maxPriorityFeePerGas: maxPriorityFeePerGas1.toString()
1660
- });
1661
- }
1662
- const swapMsgIndex = usePermit2Triple ? 2 : 1;
1663
- const audit = {
1025
+ steps.push({
1026
+ to: toRouter,
1027
+ data: dataHex,
1028
+ value: valueWei,
1029
+ routerSwap: true,
1030
+ fallbackGas: DEFAULT_UNIVERSAL_ROUTER_GAS_FALLBACK
1031
+ });
1032
+ const swapStepIndex = steps.length - 1;
1033
+ const approveMsgRawNo0x = approveData.startsWith("0x") ? approveData.slice(2) : approveData;
1034
+ const purposeSuffix = 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).";
1035
+ const buildSwapAudit = () => ({
1664
1036
  skipPermit2Batch: true,
1665
1037
  approvalPath: usePermit2Triple ? "permit2_triple" : "dispatcher",
1666
1038
  approveAmount: {
@@ -1674,9 +1046,9 @@ async function buildEvmMultisignBodyUniswapV4SkipPermit2Batch(args) {
1674
1046
  requestId: args.createSwapResponse.requestId,
1675
1047
  gasFee: args.createSwapResponse.gasFee,
1676
1048
  gasBuildSwap: {
1677
- useCustomGas,
1049
+ useCustomGas: args.useCustomGas,
1678
1050
  source: gasBuildSource,
1679
- baseGasUnits: baseGasUnits1.toString(),
1051
+ baseGasUnits: swapBaseGasUnits.toString(),
1680
1052
  ...estimateGasError != null && estimateGasError !== "" ? { estimateGasError } : {}
1681
1053
  },
1682
1054
  swap: {
@@ -1718,92 +1090,98 @@ async function buildEvmMultisignBodyUniswapV4SkipPermit2Batch(args) {
1718
1090
  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)."
1719
1091
  }
1720
1092
  }
1721
- };
1722
- const batchMeta = [
1723
- {
1724
- destinationAddress: tokenIn,
1725
- signatureText: JSON.stringify({
1726
- kind: "UniswapV4",
1727
- name: "ERC20.approve",
1728
- to: usePermit2Triple ? "allowance hub" : "dispatcher(swap.to)",
1729
- function: "approve(address spender, uint256 amount)",
1730
- spender: erc20ApproveSpender,
1731
- amountWei: approveAmountWei.toString()
1732
- }),
1733
- evm: {
1734
- type: usePermit2Triple ? "uniswap_v4_skip_permit2_approve" : "uniswap_v4_skip_permit2_dispatcher_approve",
1735
- version: 1,
1736
- chainId: String(args.chainId),
1737
- ...usePermit2Triple ? { permit2: PERMIT2_ADDRESS } : { dispatcher: toRouter }
1093
+ });
1094
+ return buildEvmMultisignBatch({
1095
+ context: {
1096
+ chainCategory: "evm",
1097
+ keyGen: args.keyGen,
1098
+ purposeText: args.purposeText,
1099
+ chainId: args.chainId,
1100
+ rpcUrl: args.rpcUrl,
1101
+ executorAddress: args.executorAddress,
1102
+ chainDetail: args.chainDetail,
1103
+ useCustomGas: args.useCustomGas,
1104
+ customGasChainDetails: args.customGasChainDetails
1105
+ },
1106
+ steps,
1107
+ purposeSuffix,
1108
+ firstMsgRawNo0x: approveMsgRawNo0x,
1109
+ destinationAddress: tokenIn,
1110
+ payableValueWei: valueWei > 0n ? valueWei : void 0,
1111
+ estimateGasForStep: async ({ step, index, publicClient, executor }) => {
1112
+ if (index !== swapStepIndex) {
1113
+ return publicClient.estimateGas({
1114
+ to: step.to,
1115
+ data: step.data,
1116
+ value: step.value,
1117
+ account: executor
1118
+ });
1738
1119
  }
1739
- }
1740
- ];
1741
- if (usePermit2Triple) {
1742
- batchMeta.push({
1743
- destinationAddress: PERMIT2_ADDRESS,
1744
- signatureText: JSON.stringify({
1745
- kind: "UniswapV4",
1746
- name: "AllowanceHub.approve",
1747
- function: "approve(address token, address spender, uint160 amount, uint48 expiration)",
1748
- token: tokenIn,
1749
- spender: permit2Spender,
1750
- amountWei: approveAmountWei.toString(),
1751
- expiration: expiration48.toString()
1752
- }),
1753
- evm: {
1754
- type: "uniswap_v4_skip_permit2_permit2_approve",
1755
- version: 1,
1756
- chainId: String(args.chainId),
1757
- permit2: PERMIT2_ADDRESS,
1758
- permit2Spender
1120
+ const r = await estimateUniswapRouterSwapGas({
1121
+ publicClient,
1122
+ executor,
1123
+ to: toRouter,
1124
+ data: dataHex,
1125
+ value: valueWei,
1126
+ swapRecord,
1127
+ useCustomGas: args.useCustomGas,
1128
+ chainDetail: args.chainDetail
1129
+ });
1130
+ gasBuildSource = r.source;
1131
+ estimateGasError = r.estimateGasError;
1132
+ swapBaseGasUnits = r.baseGasUnits;
1133
+ return r.baseGasUnits;
1134
+ },
1135
+ buildBatchMeta: ({ index }) => {
1136
+ if (index === 0) {
1137
+ return {
1138
+ signatureText: JSON.stringify({
1139
+ kind: "UniswapV4",
1140
+ name: "ERC20.approve",
1141
+ to: usePermit2Triple ? "allowance hub" : "dispatcher(swap.to)",
1142
+ function: "approve(address spender, uint256 amount)",
1143
+ spender: erc20ApproveSpender,
1144
+ amountWei: approveAmountWei.toString()
1145
+ }),
1146
+ evm: {
1147
+ type: usePermit2Triple ? "uniswap_v4_skip_permit2_approve" : "uniswap_v4_skip_permit2_dispatcher_approve",
1148
+ version: 1,
1149
+ chainId: String(args.chainId),
1150
+ ...usePermit2Triple ? { permit2: PERMIT2_ADDRESS } : { dispatcher: toRouter }
1151
+ }
1152
+ };
1759
1153
  }
1760
- });
1761
- }
1762
- batchMeta.push({
1763
- destinationAddress: toRouter,
1764
- signatureText: JSON.stringify({
1765
- kind: "UniswapV4",
1766
- name: "UniversalRouter.execute",
1767
- note: `Calldata from Trade API POST /swap; signed tx hash in messageHashes[${swapMsgIndex}].`
1768
- }),
1769
- evm: { type: "uniswap_v4_swap_tx", version: 1, chainId: String(args.chainId) },
1770
- uniswapV4: audit
1771
- });
1772
- const extraPayload = { batchMeta };
1773
- if (useCustomGas) {
1774
- const snap = args.customGasChainDetails;
1775
- if (snap && typeof snap === "object" && !Array.isArray(snap) && Object.keys(snap).length > 0) {
1776
- extraPayload.customGasChainDetails = snap;
1154
+ if (usePermit2Triple && index === 1) {
1155
+ return {
1156
+ signatureText: JSON.stringify({
1157
+ kind: "UniswapV4",
1158
+ name: "AllowanceHub.approve",
1159
+ function: "approve(address token, address spender, uint160 amount, uint48 expiration)",
1160
+ token: tokenIn,
1161
+ spender: permit2Spender,
1162
+ amountWei: approveAmountWei.toString(),
1163
+ expiration: expiration48.toString()
1164
+ }),
1165
+ evm: {
1166
+ type: "uniswap_v4_skip_permit2_permit2_approve",
1167
+ version: 1,
1168
+ chainId: String(args.chainId),
1169
+ permit2: PERMIT2_ADDRESS,
1170
+ permit2Spender
1171
+ }
1172
+ };
1173
+ }
1174
+ return {
1175
+ signatureText: JSON.stringify({
1176
+ kind: "UniswapV4",
1177
+ name: "UniversalRouter.execute",
1178
+ note: `Calldata from Trade API POST /swap; signed tx hash in messageHashes[${swapMsgIndex}].`
1179
+ }),
1180
+ evm: { type: "uniswap_v4_swap_tx", version: 1, chainId: String(args.chainId) },
1181
+ uniswapV4: buildSwapAudit()
1182
+ };
1777
1183
  }
1778
- }
1779
- const extraJSON = JSON.stringify(extraPayload);
1780
- const firstSigText = batchMeta[0].signatureText;
1781
- const bodyForSign = {
1782
- keyList,
1783
- pubKey: ph,
1784
- msgHash: messageHashes[0],
1785
- msgRaw: approveMsgRawNo0x,
1786
- messageHashes,
1787
- messageRawBatch,
1788
- destinationChainID: String(args.chainId),
1789
- destinationAddress: tokenIn,
1790
- extraJSON,
1791
- signatureText: firstSigText,
1792
- purpose: (() => {
1793
- const t = args.purposeText.trim();
1794
- 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).";
1795
- return (t ? `${t}
1796
-
1797
- ` : "") + batchDesc;
1798
- })(),
1799
- ...firstTxFeePayload,
1800
- proposalTxParams: proposalTxParamsBatch
1801
- };
1802
- if (valueWei > 0n) {
1803
- bodyForSign.value = valueWei.toString();
1804
- }
1805
- if (clientId) bodyForSign.clientId = clientId;
1806
- return { bodyForSign, messageToSign: JSON.stringify(bodyForSign) };
1184
+ });
1807
1185
  }
1808
1186
 
1809
1187
  // src/protocols/evm/uniswap-v4/swap.ts
@@ -2232,317 +1610,10 @@ var curveDao = {
2232
1610
  loadSession: loadFullCurveSessionForRpc
2233
1611
  };
2234
1612
 
2235
- // src/agent/commonParamDocs.ts
2236
- var EVM_COMMON_PARAM_DOCS = {
2237
- keyGen: {
2238
- type: "object",
2239
- required: true,
2240
- description: "MPC key slice: { pubkeyhex: string (required), keylist: string[], ClientKeys?: Record<string,string> }. Used for pubKey/keyList on POST /multiSignRequest."
2241
- },
2242
- purposeText: {
2243
- type: "string",
2244
- required: true,
2245
- description: "Human-readable purpose for the sign request. Stored in bodyForSign.purpose (may be appended with an automatic batch suffix)."
2246
- },
2247
- useCustomGas: {
2248
- type: "boolean",
2249
- required: true,
2250
- description: "When true, apply chain gas settings from chainDetail / customGasChainDetails instead of raw RPC estimates only."
2251
- },
2252
- chainId: {
2253
- type: "number",
2254
- required: true,
2255
- description: "EVM chain id (decimal). Becomes destinationChainID on the sign request."
2256
- },
2257
- rpcUrl: {
2258
- type: "string",
2259
- required: true,
2260
- description: "HTTPS JSON-RPC URL for gas estimation, nonce, and allowance reads."
2261
- },
2262
- executorAddress: {
2263
- type: "address",
2264
- required: true,
2265
- description: "MPC wallet address (from keyGen ethereumaddress) \u2014 tx sender for estimates and approvals."
2266
- },
2267
- chainDetail: {
2268
- type: "object",
2269
- required: true,
2270
- description: "Optional gas config: { legacy?, gasLimit?, gasMultiplier?, gasPrice?, baseFee?, priorityFee?, baseFeeMultiplier? }."
2271
- },
2272
- customGasChainDetails: {
2273
- type: "object",
2274
- required: false,
2275
- description: "Snapshot written to extraJSON.customGasChainDetails when useCustomGas is true."
2276
- }
2277
- };
2278
- var MULTISIGN_OUTPUT_DOC = {
2279
- description: "Unsigned mpc-auth multiSignRequest payload. The caller must sign messageToSign (MetaMask personal_sign or Ed25519) and POST { ...bodyForSign, clientSig, signedMessage: messageToSign } to /multiSignRequest.",
2280
- fields: {
2281
- bodyForSign: {
2282
- type: "object",
2283
- description: "POST body fields without clientSig: keyList, pubKey, msgHash, msgRaw, destinationChainID, purpose, extraJSON, proposalTxParams (batch), messageHashes/messageRawBatch when N>1 txs."
2284
- },
2285
- messageToSign: {
2286
- type: "string",
2287
- description: "JSON.stringify(bodyForSign) \u2014 exact string to sign before adding clientSig."
2288
- }
2289
- }
2290
- };
2291
- var MANAGEMENT_SIG_DOC = {
2292
- description: "Management POST bodies embed NodeMgtKeySig: { nonce, clientSig, nodeKey }. Sign JSON with clientSig cleared; POST with clientSig set to the Ed25519 128-hex or EIP-191 signature. Legacy Nonce/Sig/sig field names are not accepted.",
2293
- fields: {
2294
- nonce: {
2295
- type: "number",
2296
- description: "From GET /getPublicMgtKeyNonce (Ed25519) or GET /getNodeMgtKeyNonce (Ethereum NodeMgtKey)."
2297
- },
2298
- clientSig: {
2299
- type: "string",
2300
- description: "Management signature; empty string in the message to sign."
2301
- },
2302
- nodeKey: {
2303
- type: "string",
2304
- description: "Required. 128-hex MPC node id from GET /getNodeKey (no 0x prefix)."
2305
- }
2306
- },
2307
- helpers: {
2308
- managementSigFields: "Base envelope with clientSig cleared.",
2309
- buildManagementPostBody: "Spread managementSigFields then endpoint fields.",
2310
- messageToSignManagementBody: "Canonical JSON string to sign.",
2311
- withManagementClientSig: "Attach signature to POST body.",
2312
- fetchNodeKey: "GET /getNodeKey via nodeFetchWithReadAuth.",
2313
- fetchManagementNonce: "GET nonce for Ed25519 or Ethereum management key."
2314
- }
2315
- };
2316
-
2317
- // src/agent/mcpTools.ts
2318
- function paramProperties(params, includeCommon = []) {
2319
- const out = {};
2320
- for (const k of includeCommon) {
2321
- const d = EVM_COMMON_PARAM_DOCS[k];
2322
- if (d) out[k] = { type: d.type, description: d.description };
2323
- }
2324
- for (const [k, d] of Object.entries(params)) {
2325
- out[k] = { type: d.type, description: d.description };
2326
- }
2327
- return out;
2328
- }
2329
- function requiredKeys(params, includeCommon = []) {
2330
- const req = [];
2331
- for (const k of includeCommon) {
2332
- if (EVM_COMMON_PARAM_DOCS[k]?.required) req.push(k);
2333
- }
2334
- for (const [k, d] of Object.entries(params)) {
2335
- if (d.required) req.push(k);
2336
- }
2337
- return req;
2338
- }
2339
- var multisignOutputSchema = {
2340
- type: "object",
2341
- description: MULTISIGN_OUTPUT_DOC.description,
2342
- properties: {
2343
- bodyForSign: {
2344
- type: "object",
2345
- description: MULTISIGN_OUTPUT_DOC.fields.bodyForSign.description
2346
- },
2347
- messageToSign: {
2348
- type: "string",
2349
- description: MULTISIGN_OUTPUT_DOC.fields.messageToSign.description
2350
- }
2351
- },
2352
- required: ["bodyForSign", "messageToSign"]
2353
- };
2354
- var MCP_TOOL_DEFINITIONS = [
2355
- {
2356
- name: "ctm_uniswap_v4_quote",
2357
- actionId: "uniswap-v4.quote",
2358
- protocolId: "uniswap-v4",
2359
- chainCategory: "evm",
2360
- description: "Fetch a Uniswap V4 Trade API quote (POST /v1/quote). Returns classic quote JSON including quote.input/output amounts and routing. Does NOT create a sign request \u2014 use ctm_uniswap_v4_create_swap and ctm_uniswap_v4_build_swap_multisign after quoting. Requires uniswapApiKey and swapper (MPC executor address) or keyGen + managementNodeUrl to resolve swapper.",
2361
- prerequisites: ["Chain must be supported by Uniswap V4 (Universal Router map)."],
2362
- followUp: ["ctm_uniswap_v4_create_swap", "ctm_uniswap_v4_build_swap_multisign"],
2363
- handler: { importPath: "protocols/evm/uniswap-v4", exportName: "uniswapTradeQuote" },
2364
- inputSchema: {
2365
- type: "object",
2366
- properties: paramProperties({
2367
- type: { type: "string", required: true, description: "EXACT_INPUT or EXACT_OUTPUT" },
2368
- amount: { type: "string", required: true, description: "Amount in token-in base units (wei string for ERC-20)" },
2369
- tokenIn: { type: "address", required: true, description: "Input token; 0x0 for native ETH" },
2370
- tokenOut: { type: "address", required: true, description: "Output token address" },
2371
- chainId: { type: "number", required: true, description: "tokenInChainId / same-chain default" },
2372
- uniswapApiKey: { type: "string", required: true, description: "Uniswap Trade API x-api-key" },
2373
- swapper: { type: "address", required: false, description: "MPC executor; omit if keyGen + managementNodeUrl provided" },
2374
- slippage: { type: "number", required: false, description: "Slippage percent; omit for API auto slippage" }
2375
- }),
2376
- required: requiredKeys({
2377
- type: { type: "string", required: true, description: "" },
2378
- amount: { type: "string", required: true, description: "" },
2379
- tokenIn: { type: "address", required: true, description: "" },
2380
- tokenOut: { type: "address", required: true, description: "" },
2381
- chainId: { type: "number", required: true, description: "" },
2382
- uniswapApiKey: { type: "string", required: true, description: "" }
2383
- })
2384
- },
2385
- outputSchema: {
2386
- type: "object",
2387
- description: "Full Uniswap POST /quote JSON (includes nested quote object with input/output amounts)."
2388
- }
2389
- },
2390
- {
2391
- name: "ctm_uniswap_v4_create_swap",
2392
- actionId: "uniswap-v4.create-swap",
2393
- protocolId: "uniswap-v4",
2394
- chainCategory: "evm",
2395
- description: "Call Uniswap Trade API POST /v1/swap to build Universal Router calldata from a prior quote. Returns { swap: { to, data, value, gasLimit? }, requestId? }. Does NOT produce a multiSignRequest \u2014 call ctm_uniswap_v4_build_swap_multisign next.",
2396
- prerequisites: ["ctm_uniswap_v4_quote output (fullQuoteFromPermit)"],
2397
- followUp: ["ctm_uniswap_v4_build_swap_multisign"],
2398
- handler: { importPath: "protocols/evm/uniswap-v4", exportName: "uniswapCreateSwap" },
2399
- inputSchema: {
2400
- type: "object",
2401
- properties: paramProperties({
2402
- uniswapApiKey: { type: "string", required: true, description: "Uniswap Trade API key" },
2403
- fullQuoteFromPermit: { type: "object", required: true, description: "Full quote JSON from ctm_uniswap_v4_quote" },
2404
- swapTransactionDeadlineUnix: {
2405
- type: "number",
2406
- required: false,
2407
- description: "On-chain deadline unix seconds; default ~30 min from now"
2408
- },
2409
- useServerProxy: {
2410
- type: "boolean",
2411
- required: false,
2412
- description: "Set false in Node/agents; true only in browser via Next API route"
2413
- }
2414
- }),
2415
- required: requiredKeys({
2416
- uniswapApiKey: { type: "string", required: true, description: "" },
2417
- fullQuoteFromPermit: { type: "object", required: true, description: "" }
2418
- })
2419
- },
2420
- outputSchema: {
2421
- type: "object",
2422
- description: "{ swap: TransactionRequest, requestId?, gasFee? }"
2423
- }
2424
- },
2425
- {
2426
- name: "ctm_uniswap_v4_build_swap_multisign",
2427
- actionId: "uniswap-v4.swap-exact-input",
2428
- protocolId: "uniswap-v4",
2429
- chainCategory: "evm",
2430
- description: "Build mpc-auth multiSignRequest body for a Uniswap V4 swap. May batch 1\u20133 EVM txs: ERC-20 approve(s) to Permit2/router path + Universal Router swap (or 1 tx for native-in). Estimates gas, serializes unsigned txs, sets proposalTxParams. Output must be signed and POSTed to /multiSignRequest by the caller.",
2431
- prerequisites: [
2432
- "ctm_uniswap_v4_create_swap output",
2433
- "keyGen with pubkeyhex",
2434
- "executorAddress matching MPC wallet",
2435
- "RPC URL and chainDetail from node chain config"
2436
- ],
2437
- followUp: ["Sign messageToSign", "POST /multiSignRequest with clientSig and signedMessage"],
2438
- handler: { importPath: "protocols/evm/uniswap-v4", exportName: "buildEvmMultisignBodyUniswapV4SkipPermit2Batch" },
2439
- inputSchema: {
2440
- type: "object",
2441
- properties: paramProperties(
2442
- {
2443
- tokenIn: { type: "address", required: true, description: "Token in; 0x0 for native ETH" },
2444
- swap: { type: "object", required: true, description: "swap field from create_swap response" },
2445
- createSwapResponse: { type: "object", required: true, description: "Full create_swap response" },
2446
- fullQuoteSnapshot: { type: "object", required: true, description: "Quote JSON used for the swap" },
2447
- swapDeadlineUnix: { type: "number", required: true, description: "Same deadline passed to create_swap" },
2448
- slippagePercent: { type: "number", required: false, description: "Extra approve headroom for EXACT_OUTPUT" }
2449
- },
2450
- ["keyGen", "purposeText", "useCustomGas", "chainId", "rpcUrl", "executorAddress", "chainDetail", "customGasChainDetails"]
2451
- ),
2452
- required: requiredKeys(
2453
- {
2454
- tokenIn: { type: "address", required: true, description: "" },
2455
- swap: { type: "object", required: true, description: "" },
2456
- createSwapResponse: { type: "object", required: true, description: "" },
2457
- fullQuoteSnapshot: { type: "object", required: true, description: "" },
2458
- swapDeadlineUnix: { type: "number", required: true, description: "" }
2459
- },
2460
- ["keyGen", "purposeText", "useCustomGas", "chainId", "rpcUrl", "executorAddress", "chainDetail"]
2461
- )
2462
- },
2463
- outputSchema: multisignOutputSchema
2464
- },
2465
- {
2466
- name: "ctm_curve_dao_build_swap_multisign",
2467
- actionId: "curve-dao.swap",
2468
- protocolId: "curve-dao",
2469
- chainCategory: "evm",
2470
- description: "Build mpc-auth multiSignRequest for a Curve Router NG swap via @curvefi/api populateSwap. Optionally batches ERC-20 approve txs when allowance is insufficient, then exchange. Requires JSON-RPC and Curve-supported chain.",
2471
- prerequisites: ["keyGen", "executorAddress", "tokenIn/tokenOut/amountHuman/slippage", "RPC URL"],
2472
- followUp: ["Sign messageToSign", "POST /multiSignRequest with clientSig and signedMessage"],
2473
- handler: { importPath: "protocols/evm/curve-dao", exportName: "buildEvmMultisignBodyCurveDaoBatch" },
2474
- inputSchema: {
2475
- type: "object",
2476
- properties: paramProperties(
2477
- {
2478
- tokenIn: { type: "address", required: true, description: "ERC-20 sold (native in uses WETH path in UI)" },
2479
- tokenOut: { type: "string", required: true, description: "Output token or 0xeeee\u2026 native placeholder" },
2480
- amountHuman: { type: "string", required: true, description: "Human-readable amount of tokenIn" },
2481
- slippagePercent: { type: "number", required: true, description: "Slippage 0\u2013100 exclusive" }
2482
- },
2483
- ["keyGen", "purposeText", "useCustomGas", "chainId", "rpcUrl", "executorAddress", "chainDetail", "customGasChainDetails"]
2484
- ),
2485
- required: requiredKeys(
2486
- {
2487
- tokenIn: { type: "address", required: true, description: "" },
2488
- tokenOut: { type: "string", required: true, description: "" },
2489
- amountHuman: { type: "string", required: true, description: "" },
2490
- slippagePercent: { type: "number", required: true, description: "" }
2491
- },
2492
- ["keyGen", "purposeText", "useCustomGas", "chainId", "rpcUrl", "executorAddress", "chainDetail"]
2493
- )
2494
- },
2495
- outputSchema: multisignOutputSchema
2496
- }
2497
- ];
2498
- function getAgentCatalogForMcp() {
2499
- return {
2500
- tools: MCP_TOOL_DEFINITIONS,
2501
- protocols: getProtocolModules(),
2502
- commonParams: EVM_COMMON_PARAM_DOCS,
2503
- multisignOutput: MULTISIGN_OUTPUT_DOC,
2504
- managementSig: MANAGEMENT_SIG_DOC,
2505
- workflow: {
2506
- evmSwapTypical: [
2507
- "1. Quote (protocol-specific API if needed)",
2508
- "2. Build protocol calldata (e.g. create_swap)",
2509
- "3. build_*_multisign \u2192 { bodyForSign, messageToSign }",
2510
- "4. Sign messageToSign (MetaMask or Ed25519)",
2511
- "5. POST /multiSignRequest with clientSig and signedMessage"
2512
- ],
2513
- managementPostTypical: [
2514
- "1. GET /getNodeKey \u2192 nodeKey (128 hex)",
2515
- "2. GET /getPublicMgtKeyNonce or /getNodeMgtKeyNonce \u2192 nonce",
2516
- "3. buildManagementPostBody(nonce, nodeKey, { \u2026endpoint fields })",
2517
- "4. messageToSignManagementBody(body) \u2192 sign \u2192 withManagementClientSig(body, sig)",
2518
- "5. POST management route with signed body"
2519
- ]
2520
- }
2521
- };
2522
- }
2523
-
2524
- // src/agent/catalog.ts
2525
- registerProtocolModule(uniswapV4ProtocolModule);
2526
- registerProtocolModule(curveDaoProtocolModule);
2527
- function getAgentCatalog() {
2528
- return {
2529
- protocols: getProtocolModules(),
2530
- byCategory: {
2531
- evm: getActionsByChainCategory("evm"),
2532
- solana: getActionsByChainCategory("solana"),
2533
- near: getActionsByChainCategory("near")
2534
- },
2535
- uniswapV4: uniswapV4ProtocolModule,
2536
- curveDao: curveDaoProtocolModule,
2537
- /** Prefer getAgentCatalogForMcp() or getMcpToolDefinitions() for MCP servers. */
2538
- mcp: getAgentCatalogForMcp()
2539
- };
2540
- }
2541
-
2542
1613
  // src/index.ts
2543
1614
  registerProtocolModule(uniswapV4ProtocolModule);
2544
1615
  registerProtocolModule(curveDaoProtocolModule);
2545
1616
 
2546
- export { NEAR_CHAIN_CATEGORY, SOLANA_CHAIN_CATEGORY, alignEip1559FeesWithLatestBase, buildEvmMultisignBatch, buildManagementPostBody, chainSnapshotForCustomGasExtraJSON, composeFeePayloadToTxParams, coreChainCategoryModule, curveDao, curveDaoProtocolModule, evmChainCategoryModule, fetchChainFeeParams, fetchManagementNonce, fetchNodeKey, finalizeMultisign, firstClientIdFromKeyGen, gasLimitFromEstimateAndChainConfig, getActionsByChainCategory, getAgentCatalog, getProtocolModule, getProtocolModules, gweiToDecimalString, isEvmNativeToken, keyListFromKeyGen, managementSigFields, matchEvmTokenKind, mergePurposeText, messageToSignManagementBody, nearChainCategoryModule, nodeFetchWithReadAuth, normalizeManagementNodeKey, parseEvmChainIdToNumber, proposalTxParamsToFeeSnapshot, registerProtocolModule, requirePubKeyHex, routerSwapGasLimitFromEstimate, solanaChainCategoryModule, triggerTxParamsFromComposeBody, uniswapV4, uniswapV4ProtocolModule, withManagementClientSig };
1617
+ export { NEAR_CHAIN_CATEGORY, SOLANA_CHAIN_CATEGORY, buildEvmMultisignBatch, coreChainCategoryModule, curveDao, curveDaoProtocolModule, evmChainCategoryModule, finalizeMultisign, getActionsByChainCategory, getProtocolModule, getProtocolModules, isEvmNativeToken, matchEvmTokenKind, mergePurposeText, nearChainCategoryModule, parseEvmChainIdToNumber, registerProtocolModule, routerSwapGasLimitFromEstimate, solanaChainCategoryModule, uniswapV4, uniswapV4ProtocolModule };
2547
1618
  //# sourceMappingURL=index.js.map
2548
1619
  //# sourceMappingURL=index.js.map