@eulerxyz/euler-v2-sdk 0.2.3-beta → 0.2.5-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 (115) 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 +392 -0
  39. package/dist/src/services/executionService/cowExecutor.js.map +1 -0
  40. package/dist/src/services/executionService/cowSwapHelpers.d.ts +255 -0
  41. package/dist/src/services/executionService/cowSwapHelpers.d.ts.map +1 -0
  42. package/dist/src/services/executionService/cowSwapHelpers.js +525 -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 +367 -20
  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 +6 -3
  57. package/dist/src/services/executionService/index.d.ts.map +1 -1
  58. package/dist/src/services/executionService/index.js +4 -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/executionService/tokenApprovalReset.d.ts +3 -0
  65. package/dist/src/services/executionService/tokenApprovalReset.d.ts.map +1 -0
  66. package/dist/src/services/executionService/tokenApprovalReset.js +8 -0
  67. package/dist/src/services/executionService/tokenApprovalReset.js.map +1 -0
  68. package/dist/src/services/priceService/backendClient.d.ts +3 -1
  69. package/dist/src/services/priceService/backendClient.d.ts.map +1 -1
  70. package/dist/src/services/priceService/backendClient.js +30 -12
  71. package/dist/src/services/priceService/backendClient.js.map +1 -1
  72. package/dist/src/services/reulLockService/index.d.ts +3 -0
  73. package/dist/src/services/reulLockService/index.d.ts.map +1 -0
  74. package/dist/src/services/reulLockService/index.js +2 -0
  75. package/dist/src/services/reulLockService/index.js.map +1 -0
  76. package/dist/src/services/reulLockService/reulLockService.d.ts +82 -0
  77. package/dist/src/services/reulLockService/reulLockService.d.ts.map +1 -0
  78. package/dist/src/services/reulLockService/reulLockService.js +120 -0
  79. package/dist/src/services/reulLockService/reulLockService.js.map +1 -0
  80. package/dist/src/services/reulLockService/reulLockServiceTypes.d.ts +41 -0
  81. package/dist/src/services/reulLockService/reulLockServiceTypes.d.ts.map +1 -0
  82. package/dist/src/services/reulLockService/reulLockServiceTypes.js +2 -0
  83. package/dist/src/services/reulLockService/reulLockServiceTypes.js.map +1 -0
  84. package/dist/src/services/rewardsService/adapters/rewardsDirectAdapter/rewardsDirectAdapter.d.ts.map +1 -1
  85. package/dist/src/services/rewardsService/adapters/rewardsDirectAdapter/rewardsDirectAdapter.js +93 -5
  86. package/dist/src/services/rewardsService/adapters/rewardsDirectAdapter/rewardsDirectAdapter.js.map +1 -1
  87. package/dist/src/services/rewardsService/rewardsService.d.ts +1 -7
  88. package/dist/src/services/rewardsService/rewardsService.d.ts.map +1 -1
  89. package/dist/src/services/rewardsService/rewardsService.js +5 -130
  90. package/dist/src/services/rewardsService/rewardsService.js.map +1 -1
  91. package/dist/src/services/rewardsService/rewardsServiceTypes.d.ts +13 -2
  92. package/dist/src/services/rewardsService/rewardsServiceTypes.d.ts.map +1 -1
  93. package/dist/src/services/swapService/index.d.ts +3 -2
  94. package/dist/src/services/swapService/index.d.ts.map +1 -1
  95. package/dist/src/services/swapService/index.js +1 -0
  96. package/dist/src/services/swapService/index.js.map +1 -1
  97. package/dist/src/services/swapService/swapAllowlist.d.ts +16 -0
  98. package/dist/src/services/swapService/swapAllowlist.d.ts.map +1 -0
  99. package/dist/src/services/swapService/swapAllowlist.js +51 -0
  100. package/dist/src/services/swapService/swapAllowlist.js.map +1 -0
  101. package/dist/src/services/swapService/swapService.d.ts +15 -0
  102. package/dist/src/services/swapService/swapService.d.ts.map +1 -1
  103. package/dist/src/services/swapService/swapService.js +237 -12
  104. package/dist/src/services/swapService/swapService.js.map +1 -1
  105. package/dist/src/services/swapService/swapServiceTypes.d.ts +33 -0
  106. package/dist/src/services/swapService/swapServiceTypes.d.ts.map +1 -1
  107. package/dist/src/services/vaults/eVaultService/eVaultServiceConfig.d.ts +2 -2
  108. package/dist/src/services/vaults/eVaultService/eVaultServiceConfig.d.ts.map +1 -1
  109. package/dist/src/services/vaults/eulerEarnService/eulerEarnServiceConfig.d.ts +2 -2
  110. package/dist/src/services/vaults/eulerEarnService/eulerEarnServiceConfig.d.ts.map +1 -1
  111. package/dist/src/utils/fallbackAdapter.d.ts +69 -0
  112. package/dist/src/utils/fallbackAdapter.d.ts.map +1 -0
  113. package/dist/src/utils/fallbackAdapter.js +139 -0
  114. package/dist/src/utils/fallbackAdapter.js.map +1 -0
  115. package/package.json +1 -1
@@ -1,21 +1,111 @@
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, COWSWAP_ORDER_DEADLINE_SECONDS, 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
- const TOKENS_REQUIRING_ZERO_APPROVAL_RESET = {
15
- 1: [getAddress("0xdAC17F958D2ee523a2206206994597C13D831ec7")],
16
- };
17
- function requiresZeroApprovalReset(chainId, token) {
18
- return (TOKENS_REQUIRING_ZERO_APPROVAL_RESET[chainId]?.some((resetToken) => resetToken === getAddress(token)) ?? false);
16
+ import { requiresZeroApprovalReset } from "./tokenApprovalReset.js";
17
+ function isCowSwapQuote(quote) {
18
+ return quote.route?.some((hop) => hop.providerName?.toLowerCase().includes("cow"));
19
+ }
20
+ function assertNonCowSwapQuote(quote, planFunctionName, cowPlanFunctionName) {
21
+ if (!isCowSwapQuote(quote))
22
+ return;
23
+ const suffix = cowPlanFunctionName
24
+ ? ` Use ${cowPlanFunctionName} instead.`
25
+ : " Use the appropriate CoW plan function instead.";
26
+ throw new Error(`ExecutionService.${planFunctionName} does not support CoW swap quotes.${suffix}`);
27
+ }
28
+ function assertCowSwapQuote(quote, planFunctionName) {
29
+ if (isCowSwapQuote(quote))
30
+ return;
31
+ throw new Error(`ExecutionService.${planFunctionName} requires a CoW swap quote.`);
32
+ }
33
+ function parseCowSwapAmount(field, value) {
34
+ if (typeof value !== "string" || !/^\d+$/.test(value)) {
35
+ throw new Error(`CoW quote ${field} missing or invalid`);
36
+ }
37
+ return BigInt(value);
38
+ }
39
+ function normalizeCowSwapQuoteId(value) {
40
+ if (typeof value === "number" && Number.isSafeInteger(value) && value >= 0) {
41
+ return value;
42
+ }
43
+ if (typeof value === "string" && /^\d+$/.test(value)) {
44
+ const parsed = Number(value);
45
+ if (Number.isSafeInteger(parsed))
46
+ return parsed;
47
+ }
48
+ return undefined;
49
+ }
50
+ function parseSlippagePercent(slippage) {
51
+ if (!Number.isFinite(slippage) || slippage <= 0) {
52
+ return { slippageUnits: 0n, denominator: 1n };
53
+ }
54
+ const slippageString = slippage.toLocaleString("en-US", {
55
+ useGrouping: false,
56
+ maximumFractionDigits: 20,
57
+ });
58
+ const [whole = "0", fraction = ""] = slippageString.split(".");
59
+ const scale = 10n ** BigInt(fraction.length);
60
+ const slippageUnits = BigInt(whole) * scale + BigInt(fraction || "0");
61
+ return {
62
+ slippageUnits,
63
+ denominator: 100n * scale,
64
+ };
65
+ }
66
+ function reduceBySlippage(amount, slippage = 0) {
67
+ const { slippageUnits, denominator } = parseSlippagePercent(slippage);
68
+ if (slippageUnits >= denominator)
69
+ return 0n;
70
+ return (amount * (denominator - slippageUnits)) / denominator;
71
+ }
72
+ function increaseBySlippage(amount, slippage = 0) {
73
+ const { slippageUnits, denominator } = parseSlippagePercent(slippage);
74
+ return ((amount * (denominator + slippageUnits) + denominator - 1n) / denominator);
75
+ }
76
+ function getCowSwapQuoteOrderAmounts(quote, options = {}) {
77
+ const providerData = quote.providerData;
78
+ const quoteSellAmount = parseCowSwapAmount("providerData.sellAmount", providerData?.sellAmount);
79
+ const buyAmount = parseCowSwapAmount("providerData.buyAmount", providerData?.buyAmount);
80
+ const feeAmount = parseCowSwapAmount("providerData.feeAmount", providerData?.feeAmount);
81
+ const quoteId = normalizeCowSwapQuoteId(providerData?.quoteId);
82
+ if (quoteSellAmount <= 0n || buyAmount <= 0n) {
83
+ throw new Error("CoW quote order amount must be positive");
84
+ }
85
+ const slippage = options.slippage ?? quote.slippage ?? 0;
86
+ const orderSellAmount = quoteSellAmount + feeAmount;
87
+ const slippageAdjustedSellAmount = options.slippageTarget === "sellAmount"
88
+ ? increaseBySlippage(orderSellAmount, slippage)
89
+ : orderSellAmount;
90
+ const adjustedSellAmount = options.maxSellAmount !== undefined &&
91
+ slippageAdjustedSellAmount > options.maxSellAmount
92
+ ? options.maxSellAmount
93
+ : slippageAdjustedSellAmount;
94
+ const adjustedBuyAmount = options.slippageTarget === "buyAmount"
95
+ ? reduceBySlippage(buyAmount, slippage)
96
+ : buyAmount;
97
+ if (adjustedSellAmount <= 0n || adjustedBuyAmount <= 0n) {
98
+ throw new Error("CoW quote order amount must be positive");
99
+ }
100
+ return {
101
+ sellAmount: adjustedSellAmount,
102
+ buyAmount: adjustedBuyAmount,
103
+ feeAmount,
104
+ quoteId,
105
+ };
106
+ }
107
+ function getCowSwapValidTo(validTo) {
108
+ return (validTo ?? Math.floor(Date.now() / 1000) + COWSWAP_ORDER_DEADLINE_SECONDS);
19
109
  }
20
110
  function isSavingsCollateral(collateral) {
21
111
  return collateral?.source === "savings";
@@ -35,7 +125,9 @@ function hasShareConversion(vault) {
35
125
  return (typeof vault === "object" &&
36
126
  vault !== null &&
37
127
  "convertToShares" in vault &&
38
- typeof vault.convertToShares === "function");
128
+ typeof vault.convertToShares === "function" &&
129
+ "previewWithdraw" in vault &&
130
+ typeof vault.previewWithdraw === "function");
39
131
  }
40
132
  function getStateTransition(item) {
41
133
  try {
@@ -199,16 +291,57 @@ export class ExecutionService {
199
291
  }
200
292
  /** Derive storage overrides needed to simulate the plan against the current account state. */
201
293
  async deriveStateOverrides(chainId, account, transactionPlan, options) {
294
+ assertNoCowSwapPlanItems(transactionPlan, "deriveStateOverrides");
202
295
  return deriveStateOverrides(this.getSimulationContext(), chainId, account, transactionPlan, options);
203
296
  }
204
297
  /** Simulate the full transaction plan, including approval resolution and plugin-aware batch execution. */
205
298
  async simulateTransactionPlan(chainId, account, transactionPlan, options) {
299
+ assertNoCowSwapPlanItems(transactionPlan, "simulateTransactionPlan");
206
300
  const processedPlan = await this.processPlanPlugins(transactionPlan, account, chainId);
207
301
  const owner = this.getAccountOwner(account);
208
302
  return simulateTransactionPlan(this.getSimulationContext(), chainId, owner, processedPlan, options);
209
303
  }
304
+ /**
305
+ * Simulate a {@link TransactionPlanPrepared} envelope. Plugins and approval
306
+ * resolution have already run via {@link prepareTransactionPlan}, so this
307
+ * skips the plugin pipeline and uses the envelope's chainId/account context
308
+ * directly — no re-fetches of plugin-side data on each click.
309
+ */
310
+ async simulatePreparedTransactionPlan(prepared, options) {
311
+ assertNoCowSwapPlanItems(prepared.plan, "simulatePreparedTransactionPlan");
312
+ const owner = this.getAccountOwner(prepared.account);
313
+ return simulateTransactionPlan(this.getSimulationContext(), prepared.chainId, owner, prepared.plan, options);
314
+ }
315
+ /**
316
+ * Run plugins and resolve required approvals up front, packaging the result
317
+ * with its execution context. Simulate and execute can be called against the
318
+ * returned envelope repeatedly without re-running plugins or refetching
319
+ * wallet allowances.
320
+ */
321
+ async prepareTransactionPlan(args) {
322
+ const { plan, chainId, account, usePermit2 = true, unlimitedApproval = false, } = args;
323
+ assertNoCowSwapPlanItems(plan, "prepareTransactionPlan");
324
+ const processedPlan = await this.processPlanPlugins(plan, account, chainId);
325
+ const owner = this.getAccountOwner(account);
326
+ const resolvedPlan = await this.resolveRequiredApprovals({
327
+ plan: processedPlan,
328
+ chainId,
329
+ account: owner,
330
+ usePermit2,
331
+ unlimitedApproval,
332
+ });
333
+ return {
334
+ __prepared: true,
335
+ plan: resolvedPlan,
336
+ chainId,
337
+ account,
338
+ usePermit2,
339
+ unlimitedApproval,
340
+ };
341
+ }
210
342
  /** Estimate gas for the full transaction plan after applying the same simulation pipeline used for execution. */
211
343
  async estimateGasForTransactionPlan(chainId, account, transactionPlan, options) {
344
+ assertNoCowSwapPlanItems(transactionPlan, "estimateGasForTransactionPlan");
212
345
  const processedPlan = await this.processPlanPlugins(transactionPlan, account, chainId);
213
346
  const owner = this.getAccountOwner(account);
214
347
  return estimateGasForTransactionPlan(this.getSimulationContext(), chainId, owner, processedPlan, options);
@@ -219,15 +352,58 @@ export class ExecutionService {
219
352
  if (!providerService) {
220
353
  throw new Error("ExecutionService.executeTransactionPlan requires a providerService. Pass it to the ExecutionService constructor or call setProviderService().");
221
354
  }
355
+ assertNoCowSwapPlanItems(args.plan, "executeTransactionPlan");
356
+ const processedPlan = await this.processPlanPlugins(args.plan, args.account, args.chainId);
357
+ assertNoCowSwapPlanItems(processedPlan, "executeTransactionPlan");
222
358
  const helperArgs = {
223
359
  ...args,
224
- plan: await this.processPlanPlugins(args.plan, args.account, args.chainId),
360
+ plan: processedPlan,
225
361
  executionService: this,
226
362
  deploymentService: this.deploymentService,
227
363
  providerService,
228
364
  };
229
365
  return executeTransactionPlan(helperArgs);
230
366
  }
367
+ /**
368
+ * Execute a {@link TransactionPlanPrepared} envelope. Plugins and approval
369
+ * resolution were already applied by {@link prepareTransactionPlan}, so
370
+ * this skips both — no per-execute plugin re-runs or wallet re-fetch.
371
+ */
372
+ async executePreparedTransactionPlan(args) {
373
+ const { providerService } = this;
374
+ if (!providerService) {
375
+ throw new Error("ExecutionService.executePreparedTransactionPlan requires a providerService. Pass it to the ExecutionService constructor or call setProviderService().");
376
+ }
377
+ const { prepared, sendTransaction, signTypedData, onProgress } = args;
378
+ assertNoCowSwapPlanItems(prepared.plan, "executePreparedTransactionPlan");
379
+ const helperArgs = {
380
+ plan: prepared.plan,
381
+ chainId: prepared.chainId,
382
+ account: prepared.account,
383
+ usePermit2: prepared.usePermit2,
384
+ unlimitedApproval: prepared.unlimitedApproval,
385
+ sendTransaction,
386
+ signTypedData,
387
+ onProgress,
388
+ alreadyResolved: true,
389
+ executionService: this,
390
+ deploymentService: this.deploymentService,
391
+ providerService,
392
+ };
393
+ return executeTransactionPlan(helperArgs);
394
+ }
395
+ async executeCowSwapTransactionPlan(args) {
396
+ const { providerService } = this;
397
+ if (!providerService) {
398
+ throw new Error("ExecutionService.executeCowSwapTransactionPlan requires a providerService. Pass it to the ExecutionService constructor or call setProviderService().");
399
+ }
400
+ return executeCowSwapTransactionPlan({
401
+ ...args,
402
+ plan: await this.processPlanPlugins(args.plan, args.account, args.chainId),
403
+ deploymentService: this.deploymentService,
404
+ providerService,
405
+ });
406
+ }
231
407
  getSimulationContext() {
232
408
  return {
233
409
  deploymentService: this.deploymentService,
@@ -786,6 +962,9 @@ export class ExecutionService {
786
962
  });
787
963
  }
788
964
  }
965
+ else if (isCowSwapPlanItem(item)) {
966
+ throw new Error("ExecutionService.mergePlans cannot merge CoW swap plan items. Merge these plans manually to preserve CoW order boundaries.");
967
+ }
789
968
  else {
790
969
  throw new Error("ExecutionService.mergePlans cannot merge contractCall plan items. Merge these plans manually to preserve call boundaries.");
791
970
  }
@@ -882,6 +1061,7 @@ export class ExecutionService {
882
1061
  */
883
1062
  resolveRequiredApprovalsWithWallet(args) {
884
1063
  const { plan, wallet, chainId, usePermit2 = true, unlimitedApproval = false, } = args;
1064
+ assertNoCowSwapPlanItems(plan, "resolveRequiredApprovalsWithWallet");
885
1065
  const deployment = this.deploymentService.getDeployment(chainId);
886
1066
  const permit2 = deployment.addresses.coreAddrs.permit2;
887
1067
  for (const item of plan) {
@@ -988,11 +1168,17 @@ export class ExecutionService {
988
1168
  */
989
1169
  async resolveRequiredApprovals(args) {
990
1170
  const { plan, chainId, account, usePermit2 = true, unlimitedApproval = false, } = args;
1171
+ assertNoCowSwapPlanItems(plan, "resolveRequiredApprovals");
1172
+ // Filter transaction plan for only RequiredApproval items
1173
+ const requiredApprovals = plan.filter((item) => item.type === "requiredApproval");
1174
+ // No approvals in the plan → nothing to resolve, no wallet fetch needed.
1175
+ // Withdraw/redeem flows always hit this path (they don't transfer assets in).
1176
+ if (requiredApprovals.length === 0) {
1177
+ return plan;
1178
+ }
991
1179
  if (!this.walletService) {
992
1180
  throw new Error("ExecutionService.resolveRequiredApprovals requires a walletService. Pass it to the ExecutionService constructor or call setWalletService().");
993
1181
  }
994
- // Filter transaction plan for only RequiredApproval items
995
- const requiredApprovals = plan.filter((item) => item.type === "requiredApproval");
996
1182
  // Transform RequiredApprovals into AssetWithSpenders
997
1183
  const assetSpendersMap = new Map();
998
1184
  for (const approval of requiredApprovals) {
@@ -1196,7 +1382,7 @@ export class ExecutionService {
1196
1382
  * @returns Array of transaction plan items (EVC batch; no approvals needed for redeem)
1197
1383
  */
1198
1384
  planRedeem(args) {
1199
- const { vault, receiver, owner, account, disableCollateral = false, } = args;
1385
+ const { vault, receiver, owner, account, disableCollateral = false } = args;
1200
1386
  const plan = [];
1201
1387
  // Get position to check collateral state
1202
1388
  const position = account?.getPosition(owner, vault);
@@ -1218,13 +1404,14 @@ export class ExecutionService {
1218
1404
  return args.shares;
1219
1405
  const vault = position?.vault;
1220
1406
  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.");
1407
+ throw new Error("planRedeem with assets requires account position vault state with previewWithdraw. Populate the account vaults before planning asset-denominated redeem.");
1222
1408
  }
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);
1409
+ // Asset-denominated redeem must round shares UP so the user receives at
1410
+ // least the requested assets. `convertToShares` rounds down, leaving a
1411
+ // rounding dust shortfall. `previewWithdraw` mirrors the on-chain
1412
+ // previewWithdraw(uint256) — Math.mulDiv with Rounding.Ceil over the
1413
+ // virtual-deposit-adjusted total{Shares,Assets}.
1414
+ return vault.previewWithdraw(args.assets);
1228
1415
  }
1229
1416
  /**
1230
1417
  * Builds a transaction plan for borrowing from a liability vault.
@@ -1497,6 +1684,7 @@ export class ExecutionService {
1497
1684
  */
1498
1685
  planRepayWithSwap(args) {
1499
1686
  const { swapQuote, account, cleanupOnMax = false, swapperMode } = args;
1687
+ assertNonCowSwapQuote(swapQuote, "planRepayWithSwap", "planClosePositionWithCow");
1500
1688
  const plan = [];
1501
1689
  const liabilityPosition = account?.getPosition(swapQuote.accountOut, swapQuote.receiver);
1502
1690
  const fromPosition = account?.getPosition(swapQuote.accountIn, swapQuote.vaultIn);
@@ -1529,6 +1717,74 @@ export class ExecutionService {
1529
1717
  plan.push(...this.convertBatchItemsToPlan(batchItems, "repayWithSwap"));
1530
1718
  return plan;
1531
1719
  }
1720
+ planClosePositionWithCow(args) {
1721
+ const { account, swapQuote, swapperMode = SwapperMode.TARGET_DEBT, slippage, validTo, orderKind, maxSellAmount, } = args;
1722
+ assertCowSwapQuote(swapQuote, "planClosePositionWithCow");
1723
+ const isTargetDebt = swapperMode === SwapperMode.TARGET_DEBT;
1724
+ const sourcePosition = account.getPosition(swapQuote.accountIn, swapQuote.vaultIn);
1725
+ const resolvedMaxSellAmount = maxSellAmount ?? (isTargetDebt ? sourcePosition?.shares : undefined);
1726
+ const orderAmounts = getCowSwapQuoteOrderAmounts(swapQuote, {
1727
+ slippage,
1728
+ slippageTarget: "sellAmount",
1729
+ maxSellAmount: resolvedMaxSellAmount,
1730
+ });
1731
+ const resolvedValidTo = getCowSwapValidTo(validTo);
1732
+ const params = {
1733
+ chainId: account.chainId,
1734
+ sellToken: swapQuote.vaultIn,
1735
+ buyToken: swapQuote.tokenOut.address,
1736
+ sellAmount: orderAmounts.sellAmount,
1737
+ buyAmount: orderAmounts.buyAmount,
1738
+ feeAmount: orderAmounts.feeAmount,
1739
+ quoteId: orderAmounts.quoteId,
1740
+ slippageBips: Math.round((slippage ?? swapQuote.slippage) * 100),
1741
+ validTo: resolvedValidTo,
1742
+ orderKind: orderKind ?? (isTargetDebt ? "buy" : "sell"),
1743
+ wrapper: {
1744
+ owner: account.owner,
1745
+ account: swapQuote.accountOut,
1746
+ deadline: resolvedValidTo,
1747
+ borrowVault: swapQuote.receiver,
1748
+ collateralVault: swapQuote.vaultIn,
1749
+ collateralAmount: orderAmounts.sellAmount,
1750
+ },
1751
+ };
1752
+ return [
1753
+ {
1754
+ type: "cowSwap",
1755
+ kind: "closePosition",
1756
+ chainId: account.chainId,
1757
+ params,
1758
+ },
1759
+ ];
1760
+ }
1761
+ planCancelClosePositionWithCow(args) {
1762
+ const config = getCowSwapChainConfig(args.chainId);
1763
+ if (!config && (!args.wrapperAddress || args.nonceNamespace == null)) {
1764
+ throw new Error(`CoW Swap not supported on chain ${args.chainId}`);
1765
+ }
1766
+ const wrapperAddress = args.wrapperAddress ?? config?.closePositionWrapper;
1767
+ const nonceNamespace = args.nonceNamespace ??
1768
+ (wrapperAddress ? computeNonceNamespace(wrapperAddress) : undefined);
1769
+ if (nonceNamespace == null) {
1770
+ throw new Error("nonceNamespace is required when wrapperAddress is not provided");
1771
+ }
1772
+ const params = {
1773
+ chainId: args.chainId,
1774
+ owner: getAddress(args.owner),
1775
+ nonce: args.nonce,
1776
+ nonceNamespace,
1777
+ wrapperAddress,
1778
+ };
1779
+ return [
1780
+ {
1781
+ type: "cowSwap",
1782
+ kind: "cancelClosePosition",
1783
+ chainId: args.chainId,
1784
+ params,
1785
+ },
1786
+ ];
1787
+ }
1532
1788
  /**
1533
1789
  * Builds a transaction plan for depositing into a vault using tokens from the user's wallet, going through a swap.
1534
1790
  * The approval is given to SwapVerifier (not the vault), then transferFromSender is used in the batch
@@ -1544,6 +1800,7 @@ export class ExecutionService {
1544
1800
  */
1545
1801
  planDepositWithSwapFromWallet(args) {
1546
1802
  const { swapQuote, amount, tokenIn, account, enableCollateral, wrappedNativeInfo, } = args;
1803
+ assertNonCowSwapQuote(swapQuote, "planDepositWithSwapFromWallet");
1547
1804
  const plan = [];
1548
1805
  // Approval goes to the transferFromSender contract (which uses permit2 transferFrom internally)
1549
1806
  plan.push({
@@ -1585,6 +1842,7 @@ export class ExecutionService {
1585
1842
  */
1586
1843
  planSwapFromWallet(args) {
1587
1844
  const { swapQuote, amount, tokenIn, account, wrappedNativeInfo } = args;
1845
+ assertNonCowSwapQuote(swapQuote, "planSwapFromWallet");
1588
1846
  const plan = [];
1589
1847
  plan.push({
1590
1848
  type: "requiredApproval",
@@ -1605,6 +1863,7 @@ export class ExecutionService {
1605
1863
  }
1606
1864
  planSwapAndBorrowFromWallet(args) {
1607
1865
  const { swapQuote, amount, tokenIn, account, borrowVault, borrowAmount, borrowAccount = swapQuote.accountOut, receiver = account.owner, collateralVault = swapQuote.receiver, wrappedNativeInfo, } = args;
1866
+ assertNonCowSwapQuote(swapQuote, "planSwapAndBorrowFromWallet");
1608
1867
  const plan = [];
1609
1868
  plan.push({
1610
1869
  type: "requiredApproval",
@@ -1636,6 +1895,7 @@ export class ExecutionService {
1636
1895
  }
1637
1896
  planSwapAndRepayFromWallet(args) {
1638
1897
  const { swapQuote, amount, tokenIn, account, liabilityVault = swapQuote.receiver, repayAccount = swapQuote.accountOut, isMax, cleanupOnMax = false, wrappedNativeInfo, } = args;
1898
+ assertNonCowSwapQuote(swapQuote, "planSwapAndRepayFromWallet");
1639
1899
  const plan = [];
1640
1900
  plan.push({
1641
1901
  type: "requiredApproval",
@@ -1672,6 +1932,7 @@ export class ExecutionService {
1672
1932
  }
1673
1933
  planWithdrawAndSwap(args) {
1674
1934
  const { account, vault, assets, owner, swapQuote } = args;
1935
+ assertNonCowSwapQuote(args.swapQuote, "planWithdrawAndSwap");
1675
1936
  const batchItems = this.encodeWithdrawAndSwap({
1676
1937
  chainId: account.chainId,
1677
1938
  vault,
@@ -1684,6 +1945,7 @@ export class ExecutionService {
1684
1945
  }
1685
1946
  planRedeemAndSwap(args) {
1686
1947
  const { account, vault, shares, owner, swapQuote } = args;
1948
+ assertNonCowSwapQuote(args.swapQuote, "planRedeemAndSwap");
1687
1949
  const batchItems = this.encodeRedeemAndSwap({
1688
1950
  chainId: account.chainId,
1689
1951
  vault,
@@ -1704,6 +1966,7 @@ export class ExecutionService {
1704
1966
  */
1705
1967
  planSwapCollateral(args) {
1706
1968
  const { swapQuote, account, swapperMode } = args;
1969
+ assertNonCowSwapQuote(swapQuote, "planSwapCollateral", "planSwapCollateralWithCoW");
1707
1970
  const plan = [];
1708
1971
  // Check if source collateral needs to be disabled (when all is swapped)
1709
1972
  // Default: when position is not available, assume amounts are zero, so we don't disable collateral
@@ -1727,6 +1990,47 @@ export class ExecutionService {
1727
1990
  plan.push(...this.convertBatchItemsToPlan(batchItems, "swapCollateral"));
1728
1991
  return plan;
1729
1992
  }
1993
+ planSwapCollateralWithCoW(args) {
1994
+ const { swapQuote, account, slippage, validTo, disableSourceCollateral } = args;
1995
+ assertCowSwapQuote(swapQuote, "planSwapCollateralWithCoW");
1996
+ const orderAmounts = getCowSwapQuoteOrderAmounts(swapQuote, {
1997
+ slippage,
1998
+ slippageTarget: "buyAmount",
1999
+ });
2000
+ const sourcePosition = account.getPosition(swapQuote.accountIn, swapQuote.vaultIn);
2001
+ const resolvedValidTo = getCowSwapValidTo(validTo);
2002
+ const params = {
2003
+ chainId: account.chainId,
2004
+ sellToken: swapQuote.vaultIn,
2005
+ buyToken: swapQuote.receiver,
2006
+ sellAmount: orderAmounts.sellAmount,
2007
+ buyAmount: orderAmounts.buyAmount,
2008
+ feeAmount: orderAmounts.feeAmount,
2009
+ quoteId: orderAmounts.quoteId,
2010
+ slippageBips: Math.round((slippage ?? swapQuote.slippage) * 100),
2011
+ validTo: resolvedValidTo,
2012
+ wrapper: {
2013
+ owner: account.owner,
2014
+ account: swapQuote.accountOut,
2015
+ deadline: resolvedValidTo,
2016
+ fromVault: swapQuote.vaultIn,
2017
+ toVault: swapQuote.receiver,
2018
+ fromAmount: orderAmounts.sellAmount,
2019
+ disableSourceCollateral: disableSourceCollateral ??
2020
+ (sourcePosition
2021
+ ? sourcePosition.shares <= orderAmounts.sellAmount
2022
+ : false),
2023
+ },
2024
+ };
2025
+ return [
2026
+ {
2027
+ type: "cowSwap",
2028
+ kind: "swapCollateral",
2029
+ chainId: account.chainId,
2030
+ params,
2031
+ },
2032
+ ];
2033
+ }
1730
2034
  /**
1731
2035
  * Builds a transaction plan for swapping debt from one liability vault to another (borrow from source → swap → repay to destination).
1732
2036
  *
@@ -1737,6 +2041,7 @@ export class ExecutionService {
1737
2041
  */
1738
2042
  planSwapDebt(args) {
1739
2043
  const { swapQuote, account, swapperMode } = args;
2044
+ assertNonCowSwapQuote(swapQuote, "planSwapDebt");
1740
2045
  const plan = [];
1741
2046
  const liabilityPosition = account?.getPosition(swapQuote.accountIn, swapQuote.receiver);
1742
2047
  const isMax = liabilityPosition
@@ -1925,6 +2230,7 @@ export class ExecutionService {
1925
2230
  */
1926
2231
  planMultiplyWithSwap(args) {
1927
2232
  const { collateralVault, collateralAmount, collateralAsset, collateralShareSource, collateralWrappedNativeInfo, account, swapQuote, swapperMode, } = args;
2233
+ assertNonCowSwapQuote(swapQuote, "planMultiplyWithSwap", "planOpenPositionWithCoW");
1928
2234
  const plan = [];
1929
2235
  // 1. Check if collateral approval is needed (only if depositing collateral)
1930
2236
  if (!collateralShareSource && collateralAmount > 0n) {
@@ -1985,6 +2291,47 @@ export class ExecutionService {
1985
2291
  plan.push(...this.convertBatchItemsToPlan(batchItems, "multiplyWithSwap"));
1986
2292
  return plan;
1987
2293
  }
2294
+ planOpenPositionWithCoW(args) {
2295
+ const { account, collateralVault, collateralAmount, collateralAsset, swapQuote, slippage, validTo, } = args;
2296
+ assertCowSwapQuote(swapQuote, "planOpenPositionWithCoW");
2297
+ if (swapQuote.accountIn !== swapQuote.accountOut) {
2298
+ throw new Error("Account in and account out must be the same");
2299
+ }
2300
+ const orderAmounts = getCowSwapQuoteOrderAmounts(swapQuote, {
2301
+ slippage,
2302
+ slippageTarget: "buyAmount",
2303
+ });
2304
+ const resolvedValidTo = getCowSwapValidTo(validTo);
2305
+ const params = {
2306
+ chainId: account.chainId,
2307
+ sellToken: swapQuote.tokenIn.address,
2308
+ buyToken: swapQuote.receiver,
2309
+ sellAmount: orderAmounts.sellAmount,
2310
+ buyAmount: orderAmounts.buyAmount,
2311
+ feeAmount: orderAmounts.feeAmount,
2312
+ quoteId: orderAmounts.quoteId,
2313
+ slippageBips: Math.round((slippage ?? swapQuote.slippage) * 100),
2314
+ validTo: resolvedValidTo,
2315
+ collateralToken: collateralAsset,
2316
+ wrapper: {
2317
+ owner: account.owner,
2318
+ account: swapQuote.accountIn,
2319
+ deadline: resolvedValidTo,
2320
+ collateralVault,
2321
+ borrowVault: swapQuote.vaultIn,
2322
+ collateralAmount,
2323
+ borrowAmount: orderAmounts.sellAmount,
2324
+ },
2325
+ };
2326
+ return [
2327
+ {
2328
+ type: "cowSwap",
2329
+ kind: "openPosition",
2330
+ chainId: account.chainId,
2331
+ params,
2332
+ },
2333
+ ];
2334
+ }
1988
2335
  /**
1989
2336
  * Builds a transaction plan for a multiply/leverage position when liability and long asset are the same (no swap).
1990
2337
  * Flow: optional collateral deposit → enable controller → borrow to long vault → skim → enable collateral on long vault.