@eulerxyz/euler-v2-sdk 0.2.3-beta → 0.2.4-beta

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 (106) hide show
  1. package/README.md +14 -11
  2. package/dist/src/entities/ERC4626Vault.d.ts +8 -0
  3. package/dist/src/entities/ERC4626Vault.d.ts.map +1 -1
  4. package/dist/src/entities/ERC4626Vault.js +4 -0
  5. package/dist/src/entities/ERC4626Vault.js.map +1 -1
  6. package/dist/src/entities/EVault.d.ts +6 -0
  7. package/dist/src/entities/EVault.d.ts.map +1 -1
  8. package/dist/src/entities/EVault.js +11 -0
  9. package/dist/src/entities/EVault.js.map +1 -1
  10. package/dist/src/entities/SecuritizeCollateralVault.d.ts +2 -0
  11. package/dist/src/entities/SecuritizeCollateralVault.d.ts.map +1 -1
  12. package/dist/src/entities/SecuritizeCollateralVault.js +4 -0
  13. package/dist/src/entities/SecuritizeCollateralVault.js.map +1 -1
  14. package/dist/src/index.d.ts +2 -0
  15. package/dist/src/index.d.ts.map +1 -1
  16. package/dist/src/index.js +2 -0
  17. package/dist/src/index.js.map +1 -1
  18. package/dist/src/sdk/buildSDK.d.ts +8 -0
  19. package/dist/src/sdk/buildSDK.d.ts.map +1 -1
  20. package/dist/src/sdk/buildSDK.js +309 -101
  21. package/dist/src/sdk/buildSDK.js.map +1 -1
  22. package/dist/src/sdk/config.d.ts +8 -2
  23. package/dist/src/sdk/config.d.ts.map +1 -1
  24. package/dist/src/sdk/config.js +6 -1
  25. package/dist/src/sdk/config.js.map +1 -1
  26. package/dist/src/sdk/sdk.d.ts +3 -0
  27. package/dist/src/sdk/sdk.d.ts.map +1 -1
  28. package/dist/src/sdk/sdk.js +2 -0
  29. package/dist/src/sdk/sdk.js.map +1 -1
  30. package/dist/src/services/accountService/accountServiceConfig.d.ts +2 -2
  31. package/dist/src/services/accountService/accountServiceConfig.d.ts.map +1 -1
  32. package/dist/src/services/executionService/abis/cowSwapWrapperAbi.d.ts +199 -0
  33. package/dist/src/services/executionService/abis/cowSwapWrapperAbi.d.ts.map +1 -0
  34. package/dist/src/services/executionService/abis/cowSwapWrapperAbi.js +95 -0
  35. package/dist/src/services/executionService/abis/cowSwapWrapperAbi.js.map +1 -0
  36. package/dist/src/services/executionService/cowExecutor.d.ts +53 -0
  37. package/dist/src/services/executionService/cowExecutor.d.ts.map +1 -0
  38. package/dist/src/services/executionService/cowExecutor.js +390 -0
  39. package/dist/src/services/executionService/cowExecutor.js.map +1 -0
  40. package/dist/src/services/executionService/cowSwapHelpers.d.ts +254 -0
  41. package/dist/src/services/executionService/cowSwapHelpers.d.ts.map +1 -0
  42. package/dist/src/services/executionService/cowSwapHelpers.js +524 -0
  43. package/dist/src/services/executionService/cowSwapHelpers.js.map +1 -0
  44. package/dist/src/services/executionService/execute.d.ts +18 -3
  45. package/dist/src/services/executionService/execute.d.ts.map +1 -1
  46. package/dist/src/services/executionService/execute.js +9 -2
  47. package/dist/src/services/executionService/execute.js.map +1 -1
  48. package/dist/src/services/executionService/executionService.d.ts +53 -2
  49. package/dist/src/services/executionService/executionService.d.ts.map +1 -1
  50. package/dist/src/services/executionService/executionService.js +368 -15
  51. package/dist/src/services/executionService/executionService.js.map +1 -1
  52. package/dist/src/services/executionService/executionServiceTypes.d.ts +130 -3
  53. package/dist/src/services/executionService/executionServiceTypes.d.ts.map +1 -1
  54. package/dist/src/services/executionService/executionServiceTypes.js +13 -0
  55. package/dist/src/services/executionService/executionServiceTypes.js.map +1 -1
  56. package/dist/src/services/executionService/index.d.ts +5 -3
  57. package/dist/src/services/executionService/index.d.ts.map +1 -1
  58. package/dist/src/services/executionService/index.js +3 -2
  59. package/dist/src/services/executionService/index.js.map +1 -1
  60. package/dist/src/services/executionService/simulate.d.ts +1 -1
  61. package/dist/src/services/executionService/simulate.d.ts.map +1 -1
  62. package/dist/src/services/executionService/simulate.js +25 -9
  63. package/dist/src/services/executionService/simulate.js.map +1 -1
  64. package/dist/src/services/priceService/backendClient.d.ts +3 -1
  65. package/dist/src/services/priceService/backendClient.d.ts.map +1 -1
  66. package/dist/src/services/priceService/backendClient.js +30 -12
  67. package/dist/src/services/priceService/backendClient.js.map +1 -1
  68. package/dist/src/services/reulLockService/index.d.ts +3 -0
  69. package/dist/src/services/reulLockService/index.d.ts.map +1 -0
  70. package/dist/src/services/reulLockService/index.js +2 -0
  71. package/dist/src/services/reulLockService/index.js.map +1 -0
  72. package/dist/src/services/reulLockService/reulLockService.d.ts +82 -0
  73. package/dist/src/services/reulLockService/reulLockService.d.ts.map +1 -0
  74. package/dist/src/services/reulLockService/reulLockService.js +120 -0
  75. package/dist/src/services/reulLockService/reulLockService.js.map +1 -0
  76. package/dist/src/services/reulLockService/reulLockServiceTypes.d.ts +41 -0
  77. package/dist/src/services/reulLockService/reulLockServiceTypes.d.ts.map +1 -0
  78. package/dist/src/services/reulLockService/reulLockServiceTypes.js +2 -0
  79. package/dist/src/services/reulLockService/reulLockServiceTypes.js.map +1 -0
  80. package/dist/src/services/rewardsService/adapters/rewardsDirectAdapter/rewardsDirectAdapter.d.ts.map +1 -1
  81. package/dist/src/services/rewardsService/adapters/rewardsDirectAdapter/rewardsDirectAdapter.js +93 -5
  82. package/dist/src/services/rewardsService/adapters/rewardsDirectAdapter/rewardsDirectAdapter.js.map +1 -1
  83. package/dist/src/services/rewardsService/rewardsService.d.ts +1 -7
  84. package/dist/src/services/rewardsService/rewardsService.d.ts.map +1 -1
  85. package/dist/src/services/rewardsService/rewardsService.js +5 -130
  86. package/dist/src/services/rewardsService/rewardsService.js.map +1 -1
  87. package/dist/src/services/rewardsService/rewardsServiceTypes.d.ts +13 -2
  88. package/dist/src/services/rewardsService/rewardsServiceTypes.d.ts.map +1 -1
  89. package/dist/src/services/swapService/index.d.ts +2 -2
  90. package/dist/src/services/swapService/index.d.ts.map +1 -1
  91. package/dist/src/services/swapService/index.js.map +1 -1
  92. package/dist/src/services/swapService/swapService.d.ts +15 -0
  93. package/dist/src/services/swapService/swapService.d.ts.map +1 -1
  94. package/dist/src/services/swapService/swapService.js +217 -7
  95. package/dist/src/services/swapService/swapService.js.map +1 -1
  96. package/dist/src/services/swapService/swapServiceTypes.d.ts +33 -0
  97. package/dist/src/services/swapService/swapServiceTypes.d.ts.map +1 -1
  98. package/dist/src/services/vaults/eVaultService/eVaultServiceConfig.d.ts +2 -2
  99. package/dist/src/services/vaults/eVaultService/eVaultServiceConfig.d.ts.map +1 -1
  100. package/dist/src/services/vaults/eulerEarnService/eulerEarnServiceConfig.d.ts +2 -2
  101. package/dist/src/services/vaults/eulerEarnService/eulerEarnServiceConfig.d.ts.map +1 -1
  102. package/dist/src/utils/fallbackAdapter.d.ts +69 -0
  103. package/dist/src/utils/fallbackAdapter.d.ts.map +1 -0
  104. package/dist/src/utils/fallbackAdapter.js +139 -0
  105. package/dist/src/utils/fallbackAdapter.js.map +1 -0
  106. package/package.json +1 -1
@@ -1,19 +1,115 @@
1
- import { decodeFunctionData, encodeFunctionData, erc20Abi, getAddress, maxUint256, maxUint160, } from "viem";
1
+ import { decodeFunctionData, encodeFunctionData, erc20Abi, getAddress, maxUint160, maxUint256, } from "viem";
2
2
  import { resolveBorrowCollateralPositions } from "../../utils/accountPositionClassification.js";
3
+ import { SwapperMode } from "../swapService/swapServiceTypes.js";
4
+ import { adjustForInterest, getSwapInputAmount, } from "../swapService/swapVerification.js";
3
5
  import { ethereumVaultConnectorAbi } from "./abis/ethereumVaultConnectorAbi.js";
4
6
  import { eVaultAbi } from "./abis/eVaultAbi.js";
5
7
  import { permit2PermitAbi } from "./abis/permit2PermitAbi.js";
6
8
  import { swapperAbi } from "./abis/swapperAbi.js";
7
9
  import { swapVerifierAbi } from "./abis/swapVerifierAbi.js";
10
+ import { executeCowSwapTransactionPlan, } from "./cowExecutor.js";
11
+ import { computeNonceNamespace, getCowSwapChainConfig, } from "./cowSwapHelpers.js";
8
12
  import * as encodeHelpers from "./encode.js";
9
- import { isEVCBatchOperation } from "./executionServiceTypes.js";
10
13
  import { executeTransactionPlan, } from "./execute.js";
14
+ import { assertNoCowSwapPlanItems, isCowSwapPlanItem, isEVCBatchOperation, } from "./executionServiceTypes.js";
11
15
  import { deriveStateOverrides, estimateGasForTransactionPlan, simulateTransactionPlan, } from "./simulate.js";
12
- import { adjustForInterest, getSwapInputAmount, } from "../swapService/swapVerification.js";
13
- import { SwapperMode } from "../swapService/swapServiceTypes.js";
14
16
  const TOKENS_REQUIRING_ZERO_APPROVAL_RESET = {
15
17
  1: [getAddress("0xdAC17F958D2ee523a2206206994597C13D831ec7")],
16
18
  };
19
+ const COWSWAP_ORDER_DEADLINE_SECONDS = 900;
20
+ function isCowSwapQuote(quote) {
21
+ return quote.route?.some((hop) => hop.providerName?.toLowerCase().includes("cow"));
22
+ }
23
+ function assertNonCowSwapQuote(quote, planFunctionName, cowPlanFunctionName) {
24
+ if (!isCowSwapQuote(quote))
25
+ return;
26
+ const suffix = cowPlanFunctionName
27
+ ? ` Use ${cowPlanFunctionName} instead.`
28
+ : " Use the appropriate CoW plan function instead.";
29
+ throw new Error(`ExecutionService.${planFunctionName} does not support CoW swap quotes.${suffix}`);
30
+ }
31
+ function assertCowSwapQuote(quote, planFunctionName) {
32
+ if (isCowSwapQuote(quote))
33
+ return;
34
+ throw new Error(`ExecutionService.${planFunctionName} requires a CoW swap quote.`);
35
+ }
36
+ function parseCowSwapAmount(field, value) {
37
+ if (typeof value !== "string" || !/^\d+$/.test(value)) {
38
+ throw new Error(`CoW quote ${field} missing or invalid`);
39
+ }
40
+ return BigInt(value);
41
+ }
42
+ function normalizeCowSwapQuoteId(value) {
43
+ if (typeof value === "number" && Number.isSafeInteger(value) && value >= 0) {
44
+ return value;
45
+ }
46
+ if (typeof value === "string" && /^\d+$/.test(value)) {
47
+ const parsed = Number(value);
48
+ if (Number.isSafeInteger(parsed))
49
+ return parsed;
50
+ }
51
+ return undefined;
52
+ }
53
+ function parseSlippagePercent(slippage) {
54
+ if (!Number.isFinite(slippage) || slippage <= 0) {
55
+ return { slippageUnits: 0n, denominator: 1n };
56
+ }
57
+ const slippageString = slippage.toLocaleString("en-US", {
58
+ useGrouping: false,
59
+ maximumFractionDigits: 20,
60
+ });
61
+ const [whole = "0", fraction = ""] = slippageString.split(".");
62
+ const scale = 10n ** BigInt(fraction.length);
63
+ const slippageUnits = BigInt(whole) * scale + BigInt(fraction || "0");
64
+ return {
65
+ slippageUnits,
66
+ denominator: 100n * scale,
67
+ };
68
+ }
69
+ function reduceBySlippage(amount, slippage = 0) {
70
+ const { slippageUnits, denominator } = parseSlippagePercent(slippage);
71
+ if (slippageUnits >= denominator)
72
+ return 0n;
73
+ return (amount * (denominator - slippageUnits)) / denominator;
74
+ }
75
+ function increaseBySlippage(amount, slippage = 0) {
76
+ const { slippageUnits, denominator } = parseSlippagePercent(slippage);
77
+ return ((amount * (denominator + slippageUnits) + denominator - 1n) / denominator);
78
+ }
79
+ function getCowSwapQuoteOrderAmounts(quote, options = {}) {
80
+ const providerData = quote.providerData;
81
+ const quoteSellAmount = parseCowSwapAmount("providerData.sellAmount", providerData?.sellAmount);
82
+ const buyAmount = parseCowSwapAmount("providerData.buyAmount", providerData?.buyAmount);
83
+ const feeAmount = parseCowSwapAmount("providerData.feeAmount", providerData?.feeAmount);
84
+ const quoteId = normalizeCowSwapQuoteId(providerData?.quoteId);
85
+ if (quoteSellAmount <= 0n || buyAmount <= 0n) {
86
+ throw new Error("CoW quote order amount must be positive");
87
+ }
88
+ const slippage = options.slippage ?? quote.slippage ?? 0;
89
+ const orderSellAmount = quoteSellAmount + feeAmount;
90
+ const slippageAdjustedSellAmount = options.slippageTarget === "sellAmount"
91
+ ? increaseBySlippage(orderSellAmount, slippage)
92
+ : orderSellAmount;
93
+ const adjustedSellAmount = options.maxSellAmount !== undefined &&
94
+ slippageAdjustedSellAmount > options.maxSellAmount
95
+ ? options.maxSellAmount
96
+ : slippageAdjustedSellAmount;
97
+ const adjustedBuyAmount = options.slippageTarget === "buyAmount"
98
+ ? reduceBySlippage(buyAmount, slippage)
99
+ : buyAmount;
100
+ if (adjustedSellAmount <= 0n || adjustedBuyAmount <= 0n) {
101
+ throw new Error("CoW quote order amount must be positive");
102
+ }
103
+ return {
104
+ sellAmount: adjustedSellAmount,
105
+ buyAmount: adjustedBuyAmount,
106
+ feeAmount,
107
+ quoteId,
108
+ };
109
+ }
110
+ function getCowSwapValidTo(validTo) {
111
+ return (validTo ?? Math.floor(Date.now() / 1000) + COWSWAP_ORDER_DEADLINE_SECONDS);
112
+ }
17
113
  function requiresZeroApprovalReset(chainId, token) {
18
114
  return (TOKENS_REQUIRING_ZERO_APPROVAL_RESET[chainId]?.some((resetToken) => resetToken === getAddress(token)) ?? false);
19
115
  }
@@ -35,7 +131,9 @@ function hasShareConversion(vault) {
35
131
  return (typeof vault === "object" &&
36
132
  vault !== null &&
37
133
  "convertToShares" in vault &&
38
- typeof vault.convertToShares === "function");
134
+ typeof vault.convertToShares === "function" &&
135
+ "previewWithdraw" in vault &&
136
+ typeof vault.previewWithdraw === "function");
39
137
  }
40
138
  function getStateTransition(item) {
41
139
  try {
@@ -199,16 +297,57 @@ export class ExecutionService {
199
297
  }
200
298
  /** Derive storage overrides needed to simulate the plan against the current account state. */
201
299
  async deriveStateOverrides(chainId, account, transactionPlan, options) {
300
+ assertNoCowSwapPlanItems(transactionPlan, "deriveStateOverrides");
202
301
  return deriveStateOverrides(this.getSimulationContext(), chainId, account, transactionPlan, options);
203
302
  }
204
303
  /** Simulate the full transaction plan, including approval resolution and plugin-aware batch execution. */
205
304
  async simulateTransactionPlan(chainId, account, transactionPlan, options) {
305
+ assertNoCowSwapPlanItems(transactionPlan, "simulateTransactionPlan");
206
306
  const processedPlan = await this.processPlanPlugins(transactionPlan, account, chainId);
207
307
  const owner = this.getAccountOwner(account);
208
308
  return simulateTransactionPlan(this.getSimulationContext(), chainId, owner, processedPlan, options);
209
309
  }
310
+ /**
311
+ * Simulate a {@link TransactionPlanPrepared} envelope. Plugins and approval
312
+ * resolution have already run via {@link prepareTransactionPlan}, so this
313
+ * skips the plugin pipeline and uses the envelope's chainId/account context
314
+ * directly — no re-fetches of plugin-side data on each click.
315
+ */
316
+ async simulatePreparedTransactionPlan(prepared, options) {
317
+ assertNoCowSwapPlanItems(prepared.plan, "simulatePreparedTransactionPlan");
318
+ const owner = this.getAccountOwner(prepared.account);
319
+ return simulateTransactionPlan(this.getSimulationContext(), prepared.chainId, owner, prepared.plan, options);
320
+ }
321
+ /**
322
+ * Run plugins and resolve required approvals up front, packaging the result
323
+ * with its execution context. Simulate and execute can be called against the
324
+ * returned envelope repeatedly without re-running plugins or refetching
325
+ * wallet allowances.
326
+ */
327
+ async prepareTransactionPlan(args) {
328
+ const { plan, chainId, account, usePermit2 = true, unlimitedApproval = false, } = args;
329
+ assertNoCowSwapPlanItems(plan, "prepareTransactionPlan");
330
+ const processedPlan = await this.processPlanPlugins(plan, account, chainId);
331
+ const owner = this.getAccountOwner(account);
332
+ const resolvedPlan = await this.resolveRequiredApprovals({
333
+ plan: processedPlan,
334
+ chainId,
335
+ account: owner,
336
+ usePermit2,
337
+ unlimitedApproval,
338
+ });
339
+ return {
340
+ __prepared: true,
341
+ plan: resolvedPlan,
342
+ chainId,
343
+ account,
344
+ usePermit2,
345
+ unlimitedApproval,
346
+ };
347
+ }
210
348
  /** Estimate gas for the full transaction plan after applying the same simulation pipeline used for execution. */
211
349
  async estimateGasForTransactionPlan(chainId, account, transactionPlan, options) {
350
+ assertNoCowSwapPlanItems(transactionPlan, "estimateGasForTransactionPlan");
212
351
  const processedPlan = await this.processPlanPlugins(transactionPlan, account, chainId);
213
352
  const owner = this.getAccountOwner(account);
214
353
  return estimateGasForTransactionPlan(this.getSimulationContext(), chainId, owner, processedPlan, options);
@@ -219,15 +358,58 @@ export class ExecutionService {
219
358
  if (!providerService) {
220
359
  throw new Error("ExecutionService.executeTransactionPlan requires a providerService. Pass it to the ExecutionService constructor or call setProviderService().");
221
360
  }
361
+ assertNoCowSwapPlanItems(args.plan, "executeTransactionPlan");
362
+ const processedPlan = await this.processPlanPlugins(args.plan, args.account, args.chainId);
363
+ assertNoCowSwapPlanItems(processedPlan, "executeTransactionPlan");
222
364
  const helperArgs = {
223
365
  ...args,
224
- plan: await this.processPlanPlugins(args.plan, args.account, args.chainId),
366
+ plan: processedPlan,
225
367
  executionService: this,
226
368
  deploymentService: this.deploymentService,
227
369
  providerService,
228
370
  };
229
371
  return executeTransactionPlan(helperArgs);
230
372
  }
373
+ /**
374
+ * Execute a {@link TransactionPlanPrepared} envelope. Plugins and approval
375
+ * resolution were already applied by {@link prepareTransactionPlan}, so
376
+ * this skips both — no per-execute plugin re-runs or wallet re-fetch.
377
+ */
378
+ async executePreparedTransactionPlan(args) {
379
+ const { providerService } = this;
380
+ if (!providerService) {
381
+ throw new Error("ExecutionService.executePreparedTransactionPlan requires a providerService. Pass it to the ExecutionService constructor or call setProviderService().");
382
+ }
383
+ const { prepared, sendTransaction, signTypedData, onProgress } = args;
384
+ assertNoCowSwapPlanItems(prepared.plan, "executePreparedTransactionPlan");
385
+ const helperArgs = {
386
+ plan: prepared.plan,
387
+ chainId: prepared.chainId,
388
+ account: prepared.account,
389
+ usePermit2: prepared.usePermit2,
390
+ unlimitedApproval: prepared.unlimitedApproval,
391
+ sendTransaction,
392
+ signTypedData,
393
+ onProgress,
394
+ alreadyResolved: true,
395
+ executionService: this,
396
+ deploymentService: this.deploymentService,
397
+ providerService,
398
+ };
399
+ return executeTransactionPlan(helperArgs);
400
+ }
401
+ async executeCowSwapTransactionPlan(args) {
402
+ const { providerService } = this;
403
+ if (!providerService) {
404
+ throw new Error("ExecutionService.executeCowSwapTransactionPlan requires a providerService. Pass it to the ExecutionService constructor or call setProviderService().");
405
+ }
406
+ return executeCowSwapTransactionPlan({
407
+ ...args,
408
+ plan: await this.processPlanPlugins(args.plan, args.account, args.chainId),
409
+ deploymentService: this.deploymentService,
410
+ providerService,
411
+ });
412
+ }
231
413
  getSimulationContext() {
232
414
  return {
233
415
  deploymentService: this.deploymentService,
@@ -786,6 +968,9 @@ export class ExecutionService {
786
968
  });
787
969
  }
788
970
  }
971
+ else if (isCowSwapPlanItem(item)) {
972
+ throw new Error("ExecutionService.mergePlans cannot merge CoW swap plan items. Merge these plans manually to preserve CoW order boundaries.");
973
+ }
789
974
  else {
790
975
  throw new Error("ExecutionService.mergePlans cannot merge contractCall plan items. Merge these plans manually to preserve call boundaries.");
791
976
  }
@@ -882,6 +1067,7 @@ export class ExecutionService {
882
1067
  */
883
1068
  resolveRequiredApprovalsWithWallet(args) {
884
1069
  const { plan, wallet, chainId, usePermit2 = true, unlimitedApproval = false, } = args;
1070
+ assertNoCowSwapPlanItems(plan, "resolveRequiredApprovalsWithWallet");
885
1071
  const deployment = this.deploymentService.getDeployment(chainId);
886
1072
  const permit2 = deployment.addresses.coreAddrs.permit2;
887
1073
  for (const item of plan) {
@@ -988,11 +1174,17 @@ export class ExecutionService {
988
1174
  */
989
1175
  async resolveRequiredApprovals(args) {
990
1176
  const { plan, chainId, account, usePermit2 = true, unlimitedApproval = false, } = args;
1177
+ assertNoCowSwapPlanItems(plan, "resolveRequiredApprovals");
1178
+ // Filter transaction plan for only RequiredApproval items
1179
+ const requiredApprovals = plan.filter((item) => item.type === "requiredApproval");
1180
+ // No approvals in the plan → nothing to resolve, no wallet fetch needed.
1181
+ // Withdraw/redeem flows always hit this path (they don't transfer assets in).
1182
+ if (requiredApprovals.length === 0) {
1183
+ return plan;
1184
+ }
991
1185
  if (!this.walletService) {
992
1186
  throw new Error("ExecutionService.resolveRequiredApprovals requires a walletService. Pass it to the ExecutionService constructor or call setWalletService().");
993
1187
  }
994
- // Filter transaction plan for only RequiredApproval items
995
- const requiredApprovals = plan.filter((item) => item.type === "requiredApproval");
996
1188
  // Transform RequiredApprovals into AssetWithSpenders
997
1189
  const assetSpendersMap = new Map();
998
1190
  for (const approval of requiredApprovals) {
@@ -1196,7 +1388,7 @@ export class ExecutionService {
1196
1388
  * @returns Array of transaction plan items (EVC batch; no approvals needed for redeem)
1197
1389
  */
1198
1390
  planRedeem(args) {
1199
- const { vault, receiver, owner, account, disableCollateral = false, } = args;
1391
+ const { vault, receiver, owner, account, disableCollateral = false } = args;
1200
1392
  const plan = [];
1201
1393
  // Get position to check collateral state
1202
1394
  const position = account?.getPosition(owner, vault);
@@ -1218,13 +1410,14 @@ export class ExecutionService {
1218
1410
  return args.shares;
1219
1411
  const vault = position?.vault;
1220
1412
  if (!hasShareConversion(vault)) {
1221
- throw new Error("planRedeem with assets requires account position vault state with convertToShares. Populate the account vaults before planning asset-denominated redeem.");
1413
+ throw new Error("planRedeem with assets requires account position vault state with previewWithdraw. Populate the account vaults before planning asset-denominated redeem.");
1222
1414
  }
1223
- // Asset-denominated redeem intentionally rounds shares down via
1224
- // convertToShares. Calling withdraw(assets) rounds shares up, which can
1225
- // burn rounding remainders. The populated vault conversion keeps EVault
1226
- // virtual deposits in the share calculation.
1227
- return vault.convertToShares(args.assets);
1415
+ // Asset-denominated redeem must round shares UP so the user receives at
1416
+ // least the requested assets. `convertToShares` rounds down, leaving a
1417
+ // rounding dust shortfall. `previewWithdraw` mirrors the on-chain
1418
+ // previewWithdraw(uint256) — Math.mulDiv with Rounding.Ceil over the
1419
+ // virtual-deposit-adjusted total{Shares,Assets}.
1420
+ return vault.previewWithdraw(args.assets);
1228
1421
  }
1229
1422
  /**
1230
1423
  * Builds a transaction plan for borrowing from a liability vault.
@@ -1497,6 +1690,7 @@ export class ExecutionService {
1497
1690
  */
1498
1691
  planRepayWithSwap(args) {
1499
1692
  const { swapQuote, account, cleanupOnMax = false, swapperMode } = args;
1693
+ assertNonCowSwapQuote(swapQuote, "planRepayWithSwap", "planClosePositionWithCow");
1500
1694
  const plan = [];
1501
1695
  const liabilityPosition = account?.getPosition(swapQuote.accountOut, swapQuote.receiver);
1502
1696
  const fromPosition = account?.getPosition(swapQuote.accountIn, swapQuote.vaultIn);
@@ -1529,6 +1723,74 @@ export class ExecutionService {
1529
1723
  plan.push(...this.convertBatchItemsToPlan(batchItems, "repayWithSwap"));
1530
1724
  return plan;
1531
1725
  }
1726
+ planClosePositionWithCow(args) {
1727
+ const { account, swapQuote, swapperMode = SwapperMode.TARGET_DEBT, slippage, validTo, orderKind, maxSellAmount, } = args;
1728
+ assertCowSwapQuote(swapQuote, "planClosePositionWithCow");
1729
+ const isTargetDebt = swapperMode === SwapperMode.TARGET_DEBT;
1730
+ const sourcePosition = account.getPosition(swapQuote.accountIn, swapQuote.vaultIn);
1731
+ const resolvedMaxSellAmount = maxSellAmount ?? (isTargetDebt ? sourcePosition?.shares : undefined);
1732
+ const orderAmounts = getCowSwapQuoteOrderAmounts(swapQuote, {
1733
+ slippage,
1734
+ slippageTarget: "sellAmount",
1735
+ maxSellAmount: resolvedMaxSellAmount,
1736
+ });
1737
+ const resolvedValidTo = getCowSwapValidTo(validTo);
1738
+ const params = {
1739
+ chainId: account.chainId,
1740
+ sellToken: swapQuote.vaultIn,
1741
+ buyToken: swapQuote.tokenOut.address,
1742
+ sellAmount: orderAmounts.sellAmount,
1743
+ buyAmount: orderAmounts.buyAmount,
1744
+ feeAmount: orderAmounts.feeAmount,
1745
+ quoteId: orderAmounts.quoteId,
1746
+ slippageBips: Math.round((slippage ?? swapQuote.slippage) * 100),
1747
+ validTo: resolvedValidTo,
1748
+ orderKind: orderKind ?? (isTargetDebt ? "buy" : "sell"),
1749
+ wrapper: {
1750
+ owner: account.owner,
1751
+ account: swapQuote.accountOut,
1752
+ deadline: resolvedValidTo,
1753
+ borrowVault: swapQuote.receiver,
1754
+ collateralVault: swapQuote.vaultIn,
1755
+ collateralAmount: orderAmounts.sellAmount,
1756
+ },
1757
+ };
1758
+ return [
1759
+ {
1760
+ type: "cowSwap",
1761
+ kind: "closePosition",
1762
+ chainId: account.chainId,
1763
+ params,
1764
+ },
1765
+ ];
1766
+ }
1767
+ planCancelClosePositionWithCow(args) {
1768
+ const config = getCowSwapChainConfig(args.chainId);
1769
+ if (!config && (!args.wrapperAddress || args.nonceNamespace == null)) {
1770
+ throw new Error(`CoW Swap not supported on chain ${args.chainId}`);
1771
+ }
1772
+ const wrapperAddress = args.wrapperAddress ?? config?.closePositionWrapper;
1773
+ const nonceNamespace = args.nonceNamespace ??
1774
+ (wrapperAddress ? computeNonceNamespace(wrapperAddress) : undefined);
1775
+ if (nonceNamespace == null) {
1776
+ throw new Error("nonceNamespace is required when wrapperAddress is not provided");
1777
+ }
1778
+ const params = {
1779
+ chainId: args.chainId,
1780
+ owner: getAddress(args.owner),
1781
+ nonce: args.nonce,
1782
+ nonceNamespace,
1783
+ wrapperAddress,
1784
+ };
1785
+ return [
1786
+ {
1787
+ type: "cowSwap",
1788
+ kind: "cancelClosePosition",
1789
+ chainId: args.chainId,
1790
+ params,
1791
+ },
1792
+ ];
1793
+ }
1532
1794
  /**
1533
1795
  * Builds a transaction plan for depositing into a vault using tokens from the user's wallet, going through a swap.
1534
1796
  * The approval is given to SwapVerifier (not the vault), then transferFromSender is used in the batch
@@ -1544,6 +1806,7 @@ export class ExecutionService {
1544
1806
  */
1545
1807
  planDepositWithSwapFromWallet(args) {
1546
1808
  const { swapQuote, amount, tokenIn, account, enableCollateral, wrappedNativeInfo, } = args;
1809
+ assertNonCowSwapQuote(swapQuote, "planDepositWithSwapFromWallet");
1547
1810
  const plan = [];
1548
1811
  // Approval goes to the transferFromSender contract (which uses permit2 transferFrom internally)
1549
1812
  plan.push({
@@ -1585,6 +1848,7 @@ export class ExecutionService {
1585
1848
  */
1586
1849
  planSwapFromWallet(args) {
1587
1850
  const { swapQuote, amount, tokenIn, account, wrappedNativeInfo } = args;
1851
+ assertNonCowSwapQuote(swapQuote, "planSwapFromWallet");
1588
1852
  const plan = [];
1589
1853
  plan.push({
1590
1854
  type: "requiredApproval",
@@ -1605,6 +1869,7 @@ export class ExecutionService {
1605
1869
  }
1606
1870
  planSwapAndBorrowFromWallet(args) {
1607
1871
  const { swapQuote, amount, tokenIn, account, borrowVault, borrowAmount, borrowAccount = swapQuote.accountOut, receiver = account.owner, collateralVault = swapQuote.receiver, wrappedNativeInfo, } = args;
1872
+ assertNonCowSwapQuote(swapQuote, "planSwapAndBorrowFromWallet");
1608
1873
  const plan = [];
1609
1874
  plan.push({
1610
1875
  type: "requiredApproval",
@@ -1636,6 +1901,7 @@ export class ExecutionService {
1636
1901
  }
1637
1902
  planSwapAndRepayFromWallet(args) {
1638
1903
  const { swapQuote, amount, tokenIn, account, liabilityVault = swapQuote.receiver, repayAccount = swapQuote.accountOut, isMax, cleanupOnMax = false, wrappedNativeInfo, } = args;
1904
+ assertNonCowSwapQuote(swapQuote, "planSwapAndRepayFromWallet");
1639
1905
  const plan = [];
1640
1906
  plan.push({
1641
1907
  type: "requiredApproval",
@@ -1672,6 +1938,7 @@ export class ExecutionService {
1672
1938
  }
1673
1939
  planWithdrawAndSwap(args) {
1674
1940
  const { account, vault, assets, owner, swapQuote } = args;
1941
+ assertNonCowSwapQuote(args.swapQuote, "planWithdrawAndSwap");
1675
1942
  const batchItems = this.encodeWithdrawAndSwap({
1676
1943
  chainId: account.chainId,
1677
1944
  vault,
@@ -1684,6 +1951,7 @@ export class ExecutionService {
1684
1951
  }
1685
1952
  planRedeemAndSwap(args) {
1686
1953
  const { account, vault, shares, owner, swapQuote } = args;
1954
+ assertNonCowSwapQuote(args.swapQuote, "planRedeemAndSwap");
1687
1955
  const batchItems = this.encodeRedeemAndSwap({
1688
1956
  chainId: account.chainId,
1689
1957
  vault,
@@ -1704,6 +1972,7 @@ export class ExecutionService {
1704
1972
  */
1705
1973
  planSwapCollateral(args) {
1706
1974
  const { swapQuote, account, swapperMode } = args;
1975
+ assertNonCowSwapQuote(swapQuote, "planSwapCollateral", "planSwapCollateralWithCoW");
1707
1976
  const plan = [];
1708
1977
  // Check if source collateral needs to be disabled (when all is swapped)
1709
1978
  // Default: when position is not available, assume amounts are zero, so we don't disable collateral
@@ -1727,6 +1996,47 @@ export class ExecutionService {
1727
1996
  plan.push(...this.convertBatchItemsToPlan(batchItems, "swapCollateral"));
1728
1997
  return plan;
1729
1998
  }
1999
+ planSwapCollateralWithCoW(args) {
2000
+ const { swapQuote, account, slippage, validTo, disableSourceCollateral } = args;
2001
+ assertCowSwapQuote(swapQuote, "planSwapCollateralWithCoW");
2002
+ const orderAmounts = getCowSwapQuoteOrderAmounts(swapQuote, {
2003
+ slippage,
2004
+ slippageTarget: "buyAmount",
2005
+ });
2006
+ const sourcePosition = account.getPosition(swapQuote.accountIn, swapQuote.vaultIn);
2007
+ const resolvedValidTo = getCowSwapValidTo(validTo);
2008
+ const params = {
2009
+ chainId: account.chainId,
2010
+ sellToken: swapQuote.vaultIn,
2011
+ buyToken: swapQuote.receiver,
2012
+ sellAmount: orderAmounts.sellAmount,
2013
+ buyAmount: orderAmounts.buyAmount,
2014
+ feeAmount: orderAmounts.feeAmount,
2015
+ quoteId: orderAmounts.quoteId,
2016
+ slippageBips: Math.round((slippage ?? swapQuote.slippage) * 100),
2017
+ validTo: resolvedValidTo,
2018
+ wrapper: {
2019
+ owner: account.owner,
2020
+ account: swapQuote.accountOut,
2021
+ deadline: resolvedValidTo,
2022
+ fromVault: swapQuote.vaultIn,
2023
+ toVault: swapQuote.receiver,
2024
+ fromAmount: orderAmounts.sellAmount,
2025
+ disableSourceCollateral: disableSourceCollateral ??
2026
+ (sourcePosition
2027
+ ? sourcePosition.shares <= orderAmounts.sellAmount
2028
+ : false),
2029
+ },
2030
+ };
2031
+ return [
2032
+ {
2033
+ type: "cowSwap",
2034
+ kind: "swapCollateral",
2035
+ chainId: account.chainId,
2036
+ params,
2037
+ },
2038
+ ];
2039
+ }
1730
2040
  /**
1731
2041
  * Builds a transaction plan for swapping debt from one liability vault to another (borrow from source → swap → repay to destination).
1732
2042
  *
@@ -1737,6 +2047,7 @@ export class ExecutionService {
1737
2047
  */
1738
2048
  planSwapDebt(args) {
1739
2049
  const { swapQuote, account, swapperMode } = args;
2050
+ assertNonCowSwapQuote(swapQuote, "planSwapDebt");
1740
2051
  const plan = [];
1741
2052
  const liabilityPosition = account?.getPosition(swapQuote.accountIn, swapQuote.receiver);
1742
2053
  const isMax = liabilityPosition
@@ -1925,6 +2236,7 @@ export class ExecutionService {
1925
2236
  */
1926
2237
  planMultiplyWithSwap(args) {
1927
2238
  const { collateralVault, collateralAmount, collateralAsset, collateralShareSource, collateralWrappedNativeInfo, account, swapQuote, swapperMode, } = args;
2239
+ assertNonCowSwapQuote(swapQuote, "planMultiplyWithSwap", "planOpenPositionWithCoW");
1928
2240
  const plan = [];
1929
2241
  // 1. Check if collateral approval is needed (only if depositing collateral)
1930
2242
  if (!collateralShareSource && collateralAmount > 0n) {
@@ -1985,6 +2297,47 @@ export class ExecutionService {
1985
2297
  plan.push(...this.convertBatchItemsToPlan(batchItems, "multiplyWithSwap"));
1986
2298
  return plan;
1987
2299
  }
2300
+ planOpenPositionWithCoW(args) {
2301
+ const { account, collateralVault, collateralAmount, collateralAsset, swapQuote, slippage, validTo, } = args;
2302
+ assertCowSwapQuote(swapQuote, "planOpenPositionWithCoW");
2303
+ if (swapQuote.accountIn !== swapQuote.accountOut) {
2304
+ throw new Error("Account in and account out must be the same");
2305
+ }
2306
+ const orderAmounts = getCowSwapQuoteOrderAmounts(swapQuote, {
2307
+ slippage,
2308
+ slippageTarget: "buyAmount",
2309
+ });
2310
+ const resolvedValidTo = getCowSwapValidTo(validTo);
2311
+ const params = {
2312
+ chainId: account.chainId,
2313
+ sellToken: swapQuote.tokenIn.address,
2314
+ buyToken: swapQuote.receiver,
2315
+ sellAmount: orderAmounts.sellAmount,
2316
+ buyAmount: orderAmounts.buyAmount,
2317
+ feeAmount: orderAmounts.feeAmount,
2318
+ quoteId: orderAmounts.quoteId,
2319
+ slippageBips: Math.round((slippage ?? swapQuote.slippage) * 100),
2320
+ validTo: resolvedValidTo,
2321
+ collateralToken: collateralAsset,
2322
+ wrapper: {
2323
+ owner: account.owner,
2324
+ account: swapQuote.accountIn,
2325
+ deadline: resolvedValidTo,
2326
+ collateralVault,
2327
+ borrowVault: swapQuote.vaultIn,
2328
+ collateralAmount,
2329
+ borrowAmount: orderAmounts.sellAmount,
2330
+ },
2331
+ };
2332
+ return [
2333
+ {
2334
+ type: "cowSwap",
2335
+ kind: "openPosition",
2336
+ chainId: account.chainId,
2337
+ params,
2338
+ },
2339
+ ];
2340
+ }
1988
2341
  /**
1989
2342
  * Builds a transaction plan for a multiply/leverage position when liability and long asset are the same (no swap).
1990
2343
  * Flow: optional collateral deposit → enable controller → borrow to long vault → skim → enable collateral on long vault.