@continuumdao/ctm-mpc-defi 0.2.0 → 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 (100) hide show
  1. package/README.md +20 -78
  2. package/dist/agent/catalog.cjs +511 -4
  3. package/dist/agent/catalog.cjs.map +1 -1
  4. package/dist/agent/catalog.d.ts +140 -20
  5. package/dist/agent/catalog.js +501 -6
  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 -226
  16. package/dist/chains/evm/index.cjs.map +1 -1
  17. package/dist/chains/evm/index.d.ts +14 -26
  18. package/dist/chains/evm/index.js +21 -211
  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 -1868
  29. package/dist/index.cjs.map +1 -1
  30. package/dist/index.d.ts +7 -11
  31. package/dist/index.js +227 -1839
  32. package/dist/index.js.map +1 -1
  33. package/dist/protocols/evm/aave-v4/index.cjs +385 -662
  34. package/dist/protocols/evm/aave-v4/index.cjs.map +1 -1
  35. package/dist/protocols/evm/aave-v4/index.d.ts +1 -2
  36. package/dist/protocols/evm/aave-v4/index.js +385 -662
  37. package/dist/protocols/evm/aave-v4/index.js.map +1 -1
  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 +290 -402
  44. package/dist/protocols/evm/ethena/index.cjs.map +1 -1
  45. package/dist/protocols/evm/ethena/index.d.ts +1 -2
  46. package/dist/protocols/evm/ethena/index.js +291 -403
  47. package/dist/protocols/evm/ethena/index.js.map +1 -1
  48. package/dist/protocols/evm/euler-v2/index.cjs +485 -1163
  49. package/dist/protocols/evm/euler-v2/index.cjs.map +1 -1
  50. package/dist/protocols/evm/euler-v2/index.d.ts +1 -2
  51. package/dist/protocols/evm/euler-v2/index.js +486 -1164
  52. package/dist/protocols/evm/euler-v2/index.js.map +1 -1
  53. package/dist/protocols/evm/lido/index.cjs +241 -236
  54. package/dist/protocols/evm/lido/index.cjs.map +1 -1
  55. package/dist/protocols/evm/lido/index.d.ts +1 -2
  56. package/dist/protocols/evm/lido/index.js +242 -237
  57. package/dist/protocols/evm/lido/index.js.map +1 -1
  58. package/dist/protocols/evm/maple/index.cjs +310 -398
  59. package/dist/protocols/evm/maple/index.cjs.map +1 -1
  60. package/dist/protocols/evm/maple/index.d.ts +1 -2
  61. package/dist/protocols/evm/maple/index.js +311 -399
  62. package/dist/protocols/evm/maple/index.js.map +1 -1
  63. package/dist/protocols/evm/sky/index.cjs +238 -233
  64. package/dist/protocols/evm/sky/index.cjs.map +1 -1
  65. package/dist/protocols/evm/sky/index.d.ts +1 -2
  66. package/dist/protocols/evm/sky/index.js +236 -231
  67. package/dist/protocols/evm/sky/index.js.map +1 -1
  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 +7 -6
  77. package/dist/agent/catalog.d.cts +0 -939
  78. package/dist/chains/evm/index.d.cts +0 -64
  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 -16
  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/aave-v4/index.d.cts +0 -500
  89. package/dist/protocols/evm/curve-dao/index.d.cts +0 -147
  90. package/dist/protocols/evm/ethena/index.d.cts +0 -161
  91. package/dist/protocols/evm/euler-v2/index.d.cts +0 -317
  92. package/dist/protocols/evm/lido/index.d.cts +0 -120
  93. package/dist/protocols/evm/maple/index.d.cts +0 -109
  94. package/dist/protocols/evm/sky/index.d.cts +0 -218
  95. package/dist/protocols/evm/uniswap-v4/index.d.cts +0 -324
  96. package/dist/registry-BwZoE668.d.cts +0 -8
  97. package/dist/txParams-BC7ogvdR.d.cts +0 -19
  98. package/dist/txParams-BC7ogvdR.d.ts +0 -19
  99. package/dist/types-B8idm_gu.d.cts +0 -34
  100. package/dist/types-Ce2qNHai.d.ts +0 -57
@@ -1,6 +1,7 @@
1
1
  'use strict';
2
2
 
3
3
  var viem = require('viem');
4
+ var continuumNodeSdk = require('@continuumdao/continuum-node-sdk');
4
5
 
5
6
  // src/core/registry.ts
6
7
  var modules = [];
@@ -144,101 +145,239 @@ function isEvmChainInEthenaUsdeList(chainId) {
144
145
  return usdeTokenAddressOnEvmChain(chainId) != null;
145
146
  }
146
147
 
147
- // src/core/keygen.ts
148
- function firstClientIdFromKeyGen(data) {
149
- const map = data?.ClientKeys;
150
- if (!map || typeof map !== "object") return null;
151
- for (const v of Object.values(map)) {
152
- if (typeof v === "string" && v.trim()) return v.trim();
153
- }
154
- return null;
148
+ // src/core/purpose.ts
149
+ function mergePurposeText(purposeText, purposeSuffix) {
150
+ const t = purposeText.trim();
151
+ const suffix = (purposeSuffix ?? "").trim();
152
+ if (!suffix) return t;
153
+ return t ? `${t}
154
+
155
+ ${suffix}` : suffix;
155
156
  }
156
157
 
157
- // src/chains/evm/txParams.ts
158
- function gasLimitFromEstimateAndChainConfig(estimatedGas, chainGasLimit) {
159
- if (chainGasLimit == null || !Number.isFinite(chainGasLimit) || chainGasLimit <= 0) {
160
- return estimatedGas;
158
+ // src/core/envelope.ts
159
+ function finalizeMultisign(input) {
160
+ const { keyGen, destinationChainID, legs } = input;
161
+ if (legs.length === 0) {
162
+ throw new Error("finalizeMultisign requires at least one leg");
163
+ }
164
+ const ph = (keyGen.pubkeyhex ?? "").trim();
165
+ if (!ph) throw new Error("keyGen pubKey (pubkeyhex) is required");
166
+ const keyList = keyGen.keylist ?? [];
167
+ const clientId = continuumNodeSdk.getClientIdFromKeyGenResult(keyGen);
168
+ const first = legs[0];
169
+ const messageHashes = legs.map((l) => l.msgHash);
170
+ const messageRawBatch = legs.map((l) => l.msgRaw);
171
+ const batchMeta = legs.map((l) => ({
172
+ destinationAddress: l.destinationAddress,
173
+ signatureText: l.signatureText,
174
+ ...l.audit
175
+ }));
176
+ const proposalTxParams = legs.map((l) => l.proposalTxParams).filter((p) => p != null && typeof p === "object");
177
+ const extraPayload = {
178
+ batchMeta,
179
+ ...input.extraJSON ?? {}
180
+ };
181
+ const extraJSON = JSON.stringify(extraPayload);
182
+ const bodyForSign = {
183
+ keyList,
184
+ pubKey: ph,
185
+ msgHash: messageHashes[0],
186
+ msgRaw: first.msgRaw,
187
+ destinationChainID,
188
+ destinationAddress: input.destinationAddress ?? first.destinationAddress,
189
+ extraJSON,
190
+ signatureText: first.signatureText,
191
+ purpose: mergePurposeText(input.purposeText, input.purposeSuffix),
192
+ ...first.feeSnapshot
193
+ };
194
+ if (legs.length > 1) {
195
+ bodyForSign.messageHashes = messageHashes;
196
+ bodyForSign.messageRawBatch = messageRawBatch;
197
+ }
198
+ if (proposalTxParams.length > 0) {
199
+ bodyForSign.proposalTxParams = proposalTxParams;
200
+ }
201
+ const valueWei = first.valueWei;
202
+ if (valueWei != null && valueWei > 0n) {
203
+ bodyForSign.value = valueWei.toString();
204
+ }
205
+ if (clientId) bodyForSign.clientId = clientId;
206
+ return { bodyForSign, messageToSign: JSON.stringify(bodyForSign) };
207
+ }
208
+ function routerSwapGasLimitFromEstimate(estimatedGas, chainGasLimit) {
209
+ if (chainGasLimit != null && Number.isFinite(chainGasLimit) && chainGasLimit > 0) {
210
+ return continuumNodeSdk.gasLimitFromEstimateAndChainConfig(estimatedGas, chainGasLimit);
161
211
  }
162
- const cfg = BigInt(Math.floor(chainGasLimit));
163
- return cfg > estimatedGas ? cfg : estimatedGas;
212
+ return (estimatedGas * 12n + 9n) / 10n;
164
213
  }
165
- async function fetchChainFeeParams(rpcUrl, chainId) {
166
- const url = rpcUrl.trim();
167
- if (!url) return { isEip1559: false };
168
- const chainIdNum = chainId;
169
- if (Number.isNaN(chainIdNum)) return { isEip1559: false };
170
- const chain = viem.defineChain({
171
- id: chainIdNum,
172
- name: "Discovery",
214
+
215
+ // src/chains/evm/buildBatch.ts
216
+ async function buildEvmMultisignBatch(args) {
217
+ const { context, steps } = args;
218
+ const {
219
+ chainId,
220
+ rpcUrl,
221
+ executorAddress,
222
+ chainDetail,
223
+ useCustomGas,
224
+ customGasChainDetails,
225
+ keyGen,
226
+ purposeText
227
+ } = context;
228
+ if (steps.length === 0) throw new Error("buildEvmMultisignBatch requires at least one step");
229
+ const ch = viem.defineChain({
230
+ id: chainId,
231
+ name: "Destination",
173
232
  nativeCurrency: { decimals: 18, name: "Ether", symbol: "ETH" },
174
- rpcUrls: { default: { http: [url] } }
175
- });
176
- const publicClient = viem.createPublicClient({
177
- chain,
178
- transport: viem.http(url)
233
+ rpcUrls: { default: { http: [rpcUrl] } }
179
234
  });
180
- const getGasPriceGwei = async () => {
181
- const gasPriceWei = await publicClient.getGasPrice();
182
- return parseFloat(viem.formatUnits(gasPriceWei, 9));
183
- };
184
- try {
185
- const block = await publicClient.getBlock({ blockTag: "latest" });
186
- const baseFeePerGas = block?.baseFeePerGas;
187
- if (baseFeePerGas == null || baseFeePerGas === void 0) {
188
- const gasPriceGwei2 = await getGasPriceGwei();
189
- return { isEip1559: false, gasPriceGwei: gasPriceGwei2 };
235
+ const publicClient = viem.createPublicClient({ chain: ch, transport: viem.http(rpcUrl) });
236
+ const feeParams = await continuumNodeSdk.fetchChainFeeParams(rpcUrl, chainId);
237
+ const legacy = Boolean(chainDetail?.legacy) || !feeParams.isEip1559;
238
+ const latestBaseFeeWei = !legacy ? (await publicClient.getBlock({ blockTag: "latest" })).baseFeePerGas ?? 0n : 0n;
239
+ const gasLimitConfig = useCustomGas && chainDetail?.gasLimit != null ? Number(chainDetail.gasLimit) : void 0;
240
+ const chainGasLimitRouter = chainDetail?.gasLimit != null && Number.isFinite(Number(chainDetail.gasLimit)) && Number(chainDetail.gasLimit) > 0 ? Number(chainDetail.gasLimit) : void 0;
241
+ const gasFeeMultiplier = useCustomGas && chainDetail?.gasMultiplier != null ? Number(chainDetail.gasMultiplier) : void 0;
242
+ const executor = viem.getAddress(executorAddress);
243
+ const baseNonce = await publicClient.getTransactionCount({ address: executor, blockTag: "pending" });
244
+ const legs = [];
245
+ for (let i = 0; i < steps.length; i++) {
246
+ const step = steps[i];
247
+ const currentNonce = baseNonce + i;
248
+ let estimatedGas;
249
+ if (args.estimateGasForStep) {
250
+ estimatedGas = await args.estimateGasForStep({ step, index: i, publicClient, executor });
251
+ } else {
252
+ try {
253
+ estimatedGas = await publicClient.estimateGas({
254
+ to: step.to,
255
+ data: step.data,
256
+ value: step.value,
257
+ account: executor
258
+ });
259
+ } catch {
260
+ estimatedGas = step.fallbackGas ?? 100000n;
261
+ }
190
262
  }
191
- const baseFeeGwei = parseFloat(viem.formatUnits(baseFeePerGas, 9));
192
- let priorityFeeGwei;
193
- try {
194
- const priorityWei = await publicClient.estimateMaxPriorityFeePerGas();
195
- priorityFeeGwei = parseFloat(viem.formatUnits(priorityWei, 9));
196
- } catch {
263
+ let gasLimitI;
264
+ if (args.resolveGasLimit) {
265
+ gasLimitI = await args.resolveGasLimit({ step, index: i, estimatedGas, publicClient });
266
+ } else if (step.routerSwap) {
267
+ gasLimitI = routerSwapGasLimitFromEstimate(estimatedGas, chainGasLimitRouter);
268
+ } else {
269
+ gasLimitI = useCustomGas ? continuumNodeSdk.gasLimitFromEstimateAndChainConfig(estimatedGas, gasLimitConfig) : estimatedGas;
197
270
  }
198
- const gasPriceGwei = await getGasPriceGwei();
199
- return {
200
- isEip1559: true,
201
- baseFeeGwei,
202
- priorityFeeGwei,
203
- gasPriceGwei
204
- };
205
- } catch {
206
- try {
207
- const gasPriceWei = await publicClient.getGasPrice();
208
- const gasPriceGwei = parseFloat(viem.formatUnits(gasPriceWei, 9));
209
- return { isEip1559: false, gasPriceGwei };
210
- } catch {
211
- return { isEip1559: false };
271
+ let proposalTxParams;
272
+ let feeSnapshot;
273
+ let serialized;
274
+ if (legacy) {
275
+ let gasPriceWei = await publicClient.getGasPrice();
276
+ if (useCustomGas && gasFeeMultiplier != null && gasFeeMultiplier > 0) {
277
+ gasPriceWei = gasPriceWei * BigInt(100 + gasFeeMultiplier) / 100n;
278
+ }
279
+ if (useCustomGas && chainDetail?.gasPrice != null && chainDetail.gasPrice > 0) {
280
+ const configured = viem.parseGwei(continuumNodeSdk.gweiToDecimalString(Number(chainDetail.gasPrice)));
281
+ if (configured > gasPriceWei) gasPriceWei = configured;
282
+ }
283
+ serialized = viem.serializeTransaction({
284
+ type: "legacy",
285
+ to: step.to,
286
+ data: step.data,
287
+ value: step.value,
288
+ gas: gasLimitI,
289
+ gasPrice: gasPriceWei,
290
+ nonce: currentNonce,
291
+ chainId
292
+ });
293
+ proposalTxParams = {
294
+ nonce: currentNonce,
295
+ gasLimit: gasLimitI.toString(),
296
+ txType: "legacy",
297
+ gasPrice: gasPriceWei.toString()
298
+ };
299
+ feeSnapshot = continuumNodeSdk.proposalTxParamsToFeeSnapshot(proposalTxParams);
300
+ } else {
301
+ const fetchedBase = feeParams.baseFeeGwei ?? 0;
302
+ const fetchedPriority = feeParams.priorityFeeGwei ?? 0;
303
+ const configuredBase = useCustomGas && chainDetail?.baseFee != null ? Number(chainDetail.baseFee) : 0;
304
+ const configuredPriority = useCustomGas && chainDetail?.priorityFee != null ? Number(chainDetail.priorityFee) : 0;
305
+ const effectiveBaseFeeGwei = Math.max(fetchedBase, configuredBase);
306
+ const effectivePriorityFeeGwei = Math.max(fetchedPriority, configuredPriority);
307
+ const baseFeeMultiplierPct = useCustomGas && chainDetail?.baseFeeMultiplier != null ? Math.max(100, Number(chainDetail.baseFeeMultiplier)) : 100;
308
+ const baseComponentGwei = effectiveBaseFeeGwei * baseFeeMultiplierPct / 100;
309
+ const maxFeePerGasGwei = baseComponentGwei + effectivePriorityFeeGwei;
310
+ let maxPriorityFeePerGas = effectivePriorityFeeGwei > 0 ? viem.parseGwei(continuumNodeSdk.gweiToDecimalString(effectivePriorityFeeGwei)) : viem.parseGwei("1");
311
+ let maxFeePerGas = viem.parseGwei(continuumNodeSdk.gweiToDecimalString(maxFeePerGasGwei));
312
+ if (useCustomGas && gasFeeMultiplier != null && gasFeeMultiplier > 0) {
313
+ maxPriorityFeePerGas = maxPriorityFeePerGas * BigInt(100 + gasFeeMultiplier) / 100n;
314
+ maxFeePerGas = maxFeePerGas * BigInt(100 + gasFeeMultiplier) / 100n;
315
+ }
316
+ ({ maxFeePerGas, maxPriorityFeePerGas } = continuumNodeSdk.alignEip1559FeesWithLatestBase(
317
+ maxFeePerGas,
318
+ maxPriorityFeePerGas,
319
+ latestBaseFeeWei
320
+ ));
321
+ serialized = viem.serializeTransaction({
322
+ type: "eip1559",
323
+ to: step.to,
324
+ data: step.data,
325
+ value: step.value,
326
+ gas: gasLimitI,
327
+ maxFeePerGas,
328
+ maxPriorityFeePerGas,
329
+ nonce: currentNonce,
330
+ chainId
331
+ });
332
+ proposalTxParams = {
333
+ nonce: currentNonce,
334
+ gasLimit: gasLimitI.toString(),
335
+ txType: "eip1559",
336
+ maxFeePerGas: maxFeePerGas.toString(),
337
+ maxPriorityFeePerGas: maxPriorityFeePerGas.toString()
338
+ };
339
+ feeSnapshot = i === 0 ? continuumNodeSdk.proposalTxParamsToFeeSnapshot(proposalTxParams) : {};
340
+ }
341
+ const h = viem.keccak256(serialized);
342
+ const msgHash = h.startsWith("0x") ? h.slice(2) : h;
343
+ const batchMetaExtra = args.buildBatchMeta({ step, index: i, gasLimit: gasLimitI });
344
+ legs.push({
345
+ msgHash,
346
+ msgRaw: i === 0 && args.firstMsgRawNo0x != null ? args.firstMsgRawNo0x : serialized,
347
+ destinationAddress: step.to,
348
+ signatureText: typeof batchMetaExtra.signatureText === "string" ? batchMetaExtra.signatureText : JSON.stringify(batchMetaExtra.signatureText ?? {}),
349
+ audit: batchMetaExtra,
350
+ feeSnapshot: i === 0 ? feeSnapshot : {},
351
+ proposalTxParams,
352
+ valueWei: i === 0 ? step.value : void 0
353
+ });
354
+ if (i === 0 && args.firstMsgRawNo0x != null) {
355
+ legs[0].msgRaw = args.firstMsgRawNo0x;
212
356
  }
213
357
  }
214
- }
215
- function finalizeEip1559Fees(maxFeePerGas, maxPriorityFeePerGas, floor, baseWei) {
216
- let maxP = maxPriorityFeePerGas;
217
- let maxF = maxFeePerGas;
218
- if (baseWei > 0n && maxF < baseWei + maxP) {
219
- maxF = baseWei + maxP + viem.parseGwei("0.001");
220
- }
221
- if (maxF < maxP) {
222
- maxF = baseWei > 0n ? baseWei + maxP + viem.parseGwei("0.001") : maxP * 2n;
358
+ const extraJSON = {};
359
+ if (useCustomGas && customGasChainDetails && Object.keys(customGasChainDetails).length > 0) {
360
+ extraJSON.customGasChainDetails = customGasChainDetails;
361
+ }
362
+ const result = finalizeMultisign({
363
+ keyGen,
364
+ purposeText,
365
+ purposeSuffix: args.purposeSuffix,
366
+ destinationChainID: String(chainId),
367
+ destinationAddress: args.destinationAddress ?? steps[0].to,
368
+ legs,
369
+ extraJSON: Object.keys(extraJSON).length > 0 ? extraJSON : void 0
370
+ });
371
+ const pv = args.payableValueWei;
372
+ if (pv != null && pv > 0n) {
373
+ result.bodyForSign.value = pv.toString();
223
374
  }
224
- return { maxFeePerGas: maxF, maxPriorityFeePerGas: maxP };
225
- }
226
- function alignEip1559FeesWithLatestBase(maxFeePerGas, maxPriorityFeePerGas, latestBlockBaseFeeWei) {
227
- return finalizeEip1559Fees(maxFeePerGas, maxPriorityFeePerGas, null, latestBlockBaseFeeWei);
375
+ return result;
228
376
  }
229
-
230
- // src/protocols/evm/ethena/multisign.ts
231
377
  var AAVE_ERC20_APPROVE_FALLBACK = 100000n;
232
378
  var ETHENA_SUSDE_DEPOSIT_GET_SIG_GAS_FALLBACK = 1200000n;
233
379
  var ETHENA_SUSDE_DEPOSIT_FALLBACK = ETHENA_SUSDE_DEPOSIT_GET_SIG_GAS_FALLBACK;
234
380
  var ETHENA_SUSDE_REDEEM_FALLBACK = 1200000n;
235
- function gweiToDecimalString(n) {
236
- if (!Number.isFinite(n)) return "0";
237
- if (n === 0) return "0";
238
- const s = String(n);
239
- if (s.indexOf("e") !== -1 || s.indexOf("E") !== -1) return n.toFixed(9).replace(/\.?0+$/, "") || "0";
240
- return s;
241
- }
242
381
  var erc20AllowanceAbi = viem.parseAbi([
243
382
  "function allowance(address owner, address spender) view returns (uint256)",
244
383
  "function decimals() view returns (uint8)"
@@ -295,10 +434,6 @@ async function buildEvmMultisignBodyEthenaUsdeStakeToSusde(args) {
295
434
  if (args.chainId !== 1) {
296
435
  throw new Error("Ethena USDe \u2192 sUSDe stake is only supported on Ethereum mainnet (chain id 1).");
297
436
  }
298
- const ph = (args.keyGen.pubkeyhex ?? "").trim();
299
- if (!ph) throw new Error("keyGen pubKey (pubkeyhex) is required");
300
- const keyList = args.keyGen.keylist ?? [];
301
- const clientId = firstClientIdFromKeyGen(args.keyGen);
302
437
  const usde = viem.getAddress(args.usde);
303
438
  const mainnetUsde = viem.getAddress(USDE_ETHEREUM_MAINNET);
304
439
  if (usde.toLowerCase() !== mainnetUsde.toLowerCase()) {
@@ -353,141 +488,58 @@ async function buildEvmMultisignBodyEthenaUsdeStakeToSusde(args) {
353
488
  args: [amountWei, receiver]
354
489
  });
355
490
  steps.push({ kind: "deposit", to: susde, data: depositData, value: 0n });
356
- const feeParams = await fetchChainFeeParams(args.rpcUrl, 1);
357
- const legacy = Boolean(args.chainDetail?.legacy) || !feeParams.isEip1559;
358
- const latestBaseFeeWei = !legacy ? (await publicClient.getBlock({ blockTag: "latest" })).baseFeePerGas ?? 0n : 0n;
359
- const useCustomGas = args.useCustomGas;
360
- const gasLimitConfig = useCustomGas && args.chainDetail?.gasLimit != null ? Number(args.chainDetail.gasLimit) : void 0;
361
- const gasFeeMultiplier = useCustomGas && args.chainDetail?.gasMultiplier != null ? Number(args.chainDetail.gasMultiplier) : void 0;
362
- const baseNonce = await publicClient.getTransactionCount({ address: executor, blockTag: "pending" });
363
- const messageHashes = [];
364
- const messageRawBatch = [];
365
- const proposalTxParamsBatch = [];
366
- const batchMeta = [];
367
- let firstTxFeePayload = {};
368
- let firstDataNo0x = "";
369
- for (let i = 0; i < steps.length; i++) {
370
- const s = steps[i];
371
- const currentNonce = baseNonce + i;
372
- let estimatedGas;
373
- const skipDepositEstimateBecausePriorApproves = s.kind === "deposit" && i > 0;
374
- if (skipDepositEstimateBecausePriorApproves) {
375
- estimatedGas = ETHENA_SUSDE_DEPOSIT_FALLBACK;
376
- } else {
377
- try {
378
- estimatedGas = await publicClient.estimateGas({
379
- to: s.to,
380
- data: s.data,
381
- value: s.value,
382
- account: executor
383
- });
384
- } catch {
385
- estimatedGas = s.kind === "approve" ? AAVE_ERC20_APPROVE_FALLBACK : ETHENA_SUSDE_DEPOSIT_FALLBACK;
386
- }
387
- }
388
- const gasLimitI = useCustomGas ? gasLimitFromEstimateAndChainConfig(estimatedGas, gasLimitConfig) : estimatedGas;
389
- if (legacy) {
390
- let gasPriceWei = await publicClient.getGasPrice();
391
- if (useCustomGas && gasFeeMultiplier != null && gasFeeMultiplier > 0) {
392
- gasPriceWei = gasPriceWei * BigInt(100 + gasFeeMultiplier) / 100n;
393
- }
394
- if (useCustomGas && args.chainDetail?.gasPrice != null && args.chainDetail.gasPrice > 0) {
395
- const configured = viem.parseGwei(gweiToDecimalString(Number(args.chainDetail.gasPrice)));
396
- if (configured > gasPriceWei) gasPriceWei = configured;
397
- }
398
- const ser = viem.serializeTransaction({
399
- type: "legacy",
400
- to: s.to,
401
- data: s.data,
402
- value: s.value,
403
- gas: gasLimitI,
404
- gasPrice: gasPriceWei,
405
- nonce: currentNonce,
406
- chainId: 1
407
- });
408
- const h = viem.keccak256(ser);
409
- messageHashes.push(h.startsWith("0x") ? h.slice(2) : h);
410
- messageRawBatch.push(ser);
411
- proposalTxParamsBatch.push({
412
- nonce: currentNonce,
413
- gasLimit: gasLimitI.toString(),
414
- txType: "legacy",
415
- gasPrice: gasPriceWei.toString()
416
- });
417
- if (i === 0) {
418
- firstTxFeePayload = { txNonce: currentNonce, txGasLimit: gasLimitI.toString(), txGasPrice: gasPriceWei.toString() };
419
- firstDataNo0x = s.data.startsWith("0x") ? s.data.slice(2) : s.data;
420
- }
421
- } else {
422
- const fetchedBase = feeParams.baseFeeGwei ?? 0;
423
- const fetchedPriority = feeParams.priorityFeeGwei ?? 0;
424
- const configuredBase = useCustomGas && args.chainDetail?.baseFee != null ? Number(args.chainDetail.baseFee) : 0;
425
- const configuredPriority = useCustomGas && args.chainDetail?.priorityFee != null ? Number(args.chainDetail.priorityFee) : 0;
426
- const effectiveBaseFeeGwei = Math.max(fetchedBase, configuredBase);
427
- const effectivePriorityFeeGwei = Math.max(fetchedPriority, configuredPriority);
428
- const baseFeeMultiplierPct = useCustomGas && args.chainDetail?.baseFeeMultiplier != null ? Math.max(100, Number(args.chainDetail.baseFeeMultiplier)) : 100;
429
- const baseComponentGwei = effectiveBaseFeeGwei * baseFeeMultiplierPct / 100;
430
- const maxFeePerGasGwei = baseComponentGwei + effectivePriorityFeeGwei;
431
- let maxPriorityFeePerGas = effectivePriorityFeeGwei > 0 ? viem.parseGwei(gweiToDecimalString(effectivePriorityFeeGwei)) : viem.parseGwei("1");
432
- let maxFeePerGas = viem.parseGwei(gweiToDecimalString(maxFeePerGasGwei));
433
- if (useCustomGas && gasFeeMultiplier != null && gasFeeMultiplier > 0) {
434
- maxPriorityFeePerGas = maxPriorityFeePerGas * BigInt(100 + gasFeeMultiplier) / 100n;
435
- maxFeePerGas = maxFeePerGas * BigInt(100 + gasFeeMultiplier) / 100n;
491
+ const evmSteps = steps.map((s) => ({
492
+ to: s.to,
493
+ data: s.data,
494
+ value: s.value,
495
+ fallbackGas: s.kind === "approve" ? AAVE_ERC20_APPROVE_FALLBACK : ETHENA_SUSDE_DEPOSIT_FALLBACK
496
+ }));
497
+ const n = steps.length;
498
+ const purposeSuffix = n === 1 ? "Ethena: 1-tx \u2014 deposit USDe to sUSDe (allowance already set)." : `Ethena: ${n}-tx batch \u2014 approve USDe to sUSDe vault, then deposit.`;
499
+ const firstDataNo0x = evmSteps[0].data.startsWith("0x") ? evmSteps[0].data.slice(2) : evmSteps[0].data;
500
+ const gasLimitConfig = args.useCustomGas && args.chainDetail?.gasLimit != null ? Number(args.chainDetail.gasLimit) : void 0;
501
+ return buildEvmMultisignBatch({
502
+ context: {
503
+ chainCategory: "evm",
504
+ keyGen: args.keyGen,
505
+ purposeText: args.purposeText,
506
+ chainId: 1,
507
+ rpcUrl: args.rpcUrl.trim(),
508
+ executorAddress: executor,
509
+ chainDetail: args.chainDetail,
510
+ useCustomGas: args.useCustomGas,
511
+ customGasChainDetails: args.customGasChainDetails
512
+ },
513
+ steps: evmSteps,
514
+ purposeSuffix,
515
+ firstMsgRawNo0x: firstDataNo0x,
516
+ destinationAddress: steps[0].to,
517
+ resolveGasLimit: ({ index, estimatedGas }) => {
518
+ const s = steps[index];
519
+ if (s.kind === "deposit" && index > 0) {
520
+ const gas = ETHENA_SUSDE_DEPOSIT_FALLBACK;
521
+ return args.useCustomGas ? continuumNodeSdk.gasLimitFromEstimateAndChainConfig(gas, gasLimitConfig) : gas;
436
522
  }
437
- ({ maxFeePerGas, maxPriorityFeePerGas } = alignEip1559FeesWithLatestBase(
438
- maxFeePerGas,
439
- maxPriorityFeePerGas,
440
- latestBaseFeeWei
441
- ));
442
- const ser = viem.serializeTransaction({
443
- type: "eip1559",
444
- to: s.to,
445
- data: s.data,
446
- value: s.value,
447
- gas: gasLimitI,
448
- maxFeePerGas,
449
- maxPriorityFeePerGas,
450
- nonce: currentNonce,
451
- chainId: 1
452
- });
453
- const h = viem.keccak256(ser);
454
- messageHashes.push(h.startsWith("0x") ? h.slice(2) : h);
455
- messageRawBatch.push(ser);
456
- proposalTxParamsBatch.push({
457
- nonce: currentNonce,
458
- gasLimit: gasLimitI.toString(),
459
- txType: "eip1559",
460
- maxFeePerGas: maxFeePerGas.toString(),
461
- maxPriorityFeePerGas: maxPriorityFeePerGas.toString()
462
- });
463
- if (i === 0) {
464
- firstTxFeePayload = {
465
- txNonce: currentNonce,
466
- txGasLimit: gasLimitI.toString(),
467
- txMaxFeePerGas: maxFeePerGas.toString(),
468
- txMaxPriorityFeePerGas: maxPriorityFeePerGas.toString()
523
+ return estimatedGas;
524
+ },
525
+ buildBatchMeta: ({ index, gasLimit }) => {
526
+ const s = steps[index];
527
+ if (s.kind === "approve") {
528
+ return {
529
+ signatureText: JSON.stringify({
530
+ kind: "Ethena",
531
+ name: "ERC20.approve",
532
+ to: "sUSDe vault",
533
+ function: "approve(address spender, uint256 amount)",
534
+ spender: susde,
535
+ amountHuman: args.amountHuman,
536
+ note: "Allowance to stake this USDe amount (not unlimited)."
537
+ }),
538
+ evm: { type: "ethena_erc20_approve", version: 1, chainId: "1" },
539
+ ethena: { step: "approve_usde", susde }
469
540
  };
470
- firstDataNo0x = s.data.startsWith("0x") ? s.data.slice(2) : s.data;
471
541
  }
472
- }
473
- if (s.kind === "approve") {
474
- batchMeta.push({
475
- destinationAddress: usde,
476
- signatureText: JSON.stringify({
477
- kind: "Ethena",
478
- name: "ERC20.approve",
479
- to: "sUSDe vault",
480
- function: "approve(address spender, uint256 amount)",
481
- spender: susde,
482
- amountHuman: args.amountHuman,
483
- note: "Allowance to stake this USDe amount (not unlimited)."
484
- }),
485
- evm: { type: "ethena_erc20_approve", version: 1, chainId: "1" },
486
- ethena: { step: "approve_usde", susde }
487
- });
488
- } else {
489
- batchMeta.push({
490
- destinationAddress: susde,
542
+ return {
491
543
  signatureText: JSON.stringify({
492
544
  kind: "Ethena",
493
545
  name: "StakedUSDe (ERC-4626).deposit",
@@ -497,40 +549,10 @@ async function buildEvmMultisignBodyEthenaUsdeStakeToSusde(args) {
497
549
  amountHuman: args.amountHuman
498
550
  }),
499
551
  evm: { type: "ethena_susde_deposit", version: 1, chainId: "1" },
500
- ethena: { step: "deposit", vault: susde, gasBuildDeposit: { baseGasUnits: gasLimitI.toString() } }
501
- });
552
+ ethena: { step: "deposit", vault: susde, gasBuildDeposit: { baseGasUnits: gasLimit.toString() } }
553
+ };
502
554
  }
503
- }
504
- const extraPayload = { batchMeta };
505
- if (useCustomGas && args.customGasChainDetails && Object.keys(args.customGasChainDetails).length > 0) {
506
- extraPayload.customGasChainDetails = args.customGasChainDetails;
507
- }
508
- const extraJSON = JSON.stringify(extraPayload);
509
- const firstSigText = batchMeta[0].signatureText;
510
- const n = steps.length;
511
- const purposeSuffix = n === 1 ? "Ethena: 1-tx \u2014 deposit USDe to sUSDe (allowance already set)." : `Ethena: ${n}-tx batch \u2014 approve USDe to sUSDe vault, then deposit.`;
512
- const bodyForSign = {
513
- keyList,
514
- pubKey: ph,
515
- msgHash: messageHashes[0],
516
- msgRaw: firstDataNo0x,
517
- messageHashes,
518
- messageRawBatch,
519
- destinationChainID: "1",
520
- destinationAddress: steps[0].to,
521
- extraJSON,
522
- signatureText: firstSigText,
523
- purpose: (() => {
524
- const t = args.purposeText.trim();
525
- return (t ? `${t}
526
-
527
- ` : "") + purposeSuffix;
528
- })(),
529
- ...firstTxFeePayload,
530
- proposalTxParams: proposalTxParamsBatch
531
- };
532
- if (clientId) bodyForSign.clientId = clientId;
533
- return { bodyForSign, messageToSign: JSON.stringify(bodyForSign) };
555
+ });
534
556
  }
535
557
  var ETHENA_SUSDE_COOLDOWN_SHARES_FALLBACK = 1200000n;
536
558
  var ETHENA_SUSDE_UNSTAKE_CLAIM_FALLBACK = 500000n;
@@ -538,161 +560,27 @@ async function buildEthenaStakedUsdeOneTxMultisignBody(args) {
538
560
  if (args.chainId !== 1) {
539
561
  throw new Error("Ethena sUSDe exit is only supported on Ethereum mainnet (chain id 1).");
540
562
  }
541
- const ph = (args.keyGen.pubkeyhex ?? "").trim();
542
- if (!ph) throw new Error("keyGen pubKey (pubkeyhex) is required");
543
- const keyList = args.keyGen.keylist ?? [];
544
- const clientId = firstClientIdFromKeyGen(args.keyGen);
545
- const { executor } = args;
546
- const steps = [args.step];
547
- const feeParams = await fetchChainFeeParams(args.rpcUrl, 1);
548
- const legacy = Boolean(args.chainDetail?.legacy) || !feeParams.isEip1559;
549
- const useCustomGas = args.useCustomGas;
550
- const gasLimitConfig = useCustomGas && args.chainDetail?.gasLimit != null ? Number(args.chainDetail.gasLimit) : void 0;
551
- const gasFeeMultiplier = useCustomGas && args.chainDetail?.gasMultiplier != null ? Number(args.chainDetail.gasMultiplier) : void 0;
552
- const ch = viem.defineChain({
553
- id: 1,
554
- name: "Ethereum",
555
- nativeCurrency: { decimals: 18, name: "Ether", symbol: "ETH" },
556
- rpcUrls: { default: { http: [args.rpcUrl.trim()] } }
563
+ const step = args.step;
564
+ const evmSteps = [{ to: step.to, data: step.data, value: step.value, fallbackGas: args.gasFallback }];
565
+ const firstDataNo0x = step.data.startsWith("0x") ? step.data.slice(2) : step.data;
566
+ return buildEvmMultisignBatch({
567
+ context: {
568
+ chainCategory: "evm",
569
+ keyGen: args.keyGen,
570
+ purposeText: args.purposeText,
571
+ chainId: 1,
572
+ rpcUrl: args.rpcUrl.trim(),
573
+ executorAddress: args.executor,
574
+ chainDetail: args.chainDetail,
575
+ useCustomGas: args.useCustomGas,
576
+ customGasChainDetails: args.customGasChainDetails
577
+ },
578
+ steps: evmSteps,
579
+ purposeSuffix: args.purposeSuffix,
580
+ firstMsgRawNo0x: firstDataNo0x,
581
+ destinationAddress: step.to,
582
+ buildBatchMeta: ({ gasLimit }) => args.makeBatchMeta({ gasLimitI: gasLimit })
557
583
  });
558
- const publicClient = viem.createPublicClient({ chain: ch, transport: viem.http(args.rpcUrl.trim()) });
559
- const latestBaseFeeWei = !legacy ? (await publicClient.getBlock({ blockTag: "latest" })).baseFeePerGas ?? 0n : 0n;
560
- const baseNonce = await publicClient.getTransactionCount({ address: executor, blockTag: "pending" });
561
- const messageHashes = [];
562
- const messageRawBatch = [];
563
- const proposalTxParamsBatch = [];
564
- const batchMeta = [];
565
- let firstTxFeePayload = {};
566
- let firstDataNo0x = "";
567
- for (let i = 0; i < steps.length; i++) {
568
- const s = steps[i];
569
- const currentNonce = baseNonce + i;
570
- let estimatedGas;
571
- try {
572
- estimatedGas = await publicClient.estimateGas({
573
- to: s.to,
574
- data: s.data,
575
- value: s.value,
576
- account: executor
577
- });
578
- } catch {
579
- estimatedGas = args.gasFallback;
580
- }
581
- const gasLimitI = useCustomGas ? gasLimitFromEstimateAndChainConfig(estimatedGas, gasLimitConfig) : estimatedGas;
582
- if (legacy) {
583
- let gasPriceWei = await publicClient.getGasPrice();
584
- if (useCustomGas && gasFeeMultiplier != null && gasFeeMultiplier > 0) {
585
- gasPriceWei = gasPriceWei * BigInt(100 + gasFeeMultiplier) / 100n;
586
- }
587
- if (useCustomGas && args.chainDetail?.gasPrice != null && args.chainDetail.gasPrice > 0) {
588
- const configured = viem.parseGwei(gweiToDecimalString(Number(args.chainDetail.gasPrice)));
589
- if (configured > gasPriceWei) gasPriceWei = configured;
590
- }
591
- const ser = viem.serializeTransaction({
592
- type: "legacy",
593
- to: s.to,
594
- data: s.data,
595
- value: s.value,
596
- gas: gasLimitI,
597
- gasPrice: gasPriceWei,
598
- nonce: currentNonce,
599
- chainId: 1
600
- });
601
- const h = viem.keccak256(ser);
602
- messageHashes.push(h.startsWith("0x") ? h.slice(2) : h);
603
- messageRawBatch.push(ser);
604
- proposalTxParamsBatch.push({
605
- nonce: currentNonce,
606
- gasLimit: gasLimitI.toString(),
607
- txType: "legacy",
608
- gasPrice: gasPriceWei.toString()
609
- });
610
- if (i === 0) {
611
- firstTxFeePayload = { txNonce: currentNonce, txGasLimit: gasLimitI.toString(), txGasPrice: gasPriceWei.toString() };
612
- firstDataNo0x = s.data.startsWith("0x") ? s.data.slice(2) : s.data;
613
- }
614
- } else {
615
- const fetchedBase = feeParams.baseFeeGwei ?? 0;
616
- const fetchedPriority = feeParams.priorityFeeGwei ?? 0;
617
- const configuredBase = useCustomGas && args.chainDetail?.baseFee != null ? Number(args.chainDetail.baseFee) : 0;
618
- const configuredPriority = useCustomGas && args.chainDetail?.priorityFee != null ? Number(args.chainDetail.priorityFee) : 0;
619
- const effectiveBaseFeeGwei = Math.max(fetchedBase, configuredBase);
620
- const effectivePriorityFeeGwei = Math.max(fetchedPriority, configuredPriority);
621
- const baseFeeMultiplierPct = useCustomGas && args.chainDetail?.baseFeeMultiplier != null ? Math.max(100, Number(args.chainDetail.baseFeeMultiplier)) : 100;
622
- const baseComponentGwei = effectiveBaseFeeGwei * baseFeeMultiplierPct / 100;
623
- const maxFeePerGasGwei = baseComponentGwei + effectivePriorityFeeGwei;
624
- let maxPriorityFeePerGas = effectivePriorityFeeGwei > 0 ? viem.parseGwei(gweiToDecimalString(effectivePriorityFeeGwei)) : viem.parseGwei("1");
625
- let maxFeePerGas = viem.parseGwei(gweiToDecimalString(maxFeePerGasGwei));
626
- if (useCustomGas && gasFeeMultiplier != null && gasFeeMultiplier > 0) {
627
- maxPriorityFeePerGas = maxPriorityFeePerGas * BigInt(100 + gasFeeMultiplier) / 100n;
628
- maxFeePerGas = maxFeePerGas * BigInt(100 + gasFeeMultiplier) / 100n;
629
- }
630
- ({ maxFeePerGas, maxPriorityFeePerGas } = alignEip1559FeesWithLatestBase(
631
- maxFeePerGas,
632
- maxPriorityFeePerGas,
633
- latestBaseFeeWei
634
- ));
635
- const ser = viem.serializeTransaction({
636
- type: "eip1559",
637
- to: s.to,
638
- data: s.data,
639
- value: s.value,
640
- gas: gasLimitI,
641
- maxFeePerGas,
642
- maxPriorityFeePerGas,
643
- nonce: currentNonce,
644
- chainId: 1
645
- });
646
- const h = viem.keccak256(ser);
647
- messageHashes.push(h.startsWith("0x") ? h.slice(2) : h);
648
- messageRawBatch.push(ser);
649
- proposalTxParamsBatch.push({
650
- nonce: currentNonce,
651
- gasLimit: gasLimitI.toString(),
652
- txType: "eip1559",
653
- maxFeePerGas: maxFeePerGas.toString(),
654
- maxPriorityFeePerGas: maxPriorityFeePerGas.toString()
655
- });
656
- if (i === 0) {
657
- firstTxFeePayload = {
658
- txNonce: currentNonce,
659
- txGasLimit: gasLimitI.toString(),
660
- txMaxFeePerGas: maxFeePerGas.toString(),
661
- txMaxPriorityFeePerGas: maxPriorityFeePerGas.toString()
662
- };
663
- firstDataNo0x = s.data.startsWith("0x") ? s.data.slice(2) : s.data;
664
- }
665
- }
666
- batchMeta.push(args.makeBatchMeta({ gasLimitI }));
667
- }
668
- const extraPayload = { batchMeta };
669
- if (useCustomGas && args.customGasChainDetails && Object.keys(args.customGasChainDetails).length > 0) {
670
- extraPayload.customGasChainDetails = args.customGasChainDetails;
671
- }
672
- const extraJSON = JSON.stringify(extraPayload);
673
- const firstSigText = batchMeta[0].signatureText;
674
- const bodyForSign = {
675
- keyList,
676
- pubKey: ph,
677
- msgHash: messageHashes[0],
678
- msgRaw: firstDataNo0x,
679
- messageHashes,
680
- messageRawBatch,
681
- destinationChainID: "1",
682
- destinationAddress: steps[0].to,
683
- extraJSON,
684
- signatureText: firstSigText,
685
- purpose: (() => {
686
- const t = args.purposeText.trim();
687
- return (t ? `${t}
688
-
689
- ` : "") + args.purposeSuffix;
690
- })(),
691
- ...firstTxFeePayload,
692
- proposalTxParams: proposalTxParamsBatch
693
- };
694
- if (clientId) bodyForSign.clientId = clientId;
695
- return { bodyForSign, messageToSign: JSON.stringify(bodyForSign) };
696
584
  }
697
585
  async function buildEvmMultisignBodyEthenaSusdeRedeemToUsde(args) {
698
586
  if (args.chainId !== 1) {