@eulerxyz/euler-v2-sdk 0.1.2-beta → 0.2.0-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 (235) hide show
  1. package/README.md +24 -25
  2. package/dist/src/entities/Account.d.ts +48 -47
  3. package/dist/src/entities/Account.d.ts.map +1 -1
  4. package/dist/src/entities/Account.js +85 -64
  5. package/dist/src/entities/Account.js.map +1 -1
  6. package/dist/src/entities/ERC4626Vault.d.ts +6 -5
  7. package/dist/src/entities/ERC4626Vault.d.ts.map +1 -1
  8. package/dist/src/entities/ERC4626Vault.js +13 -6
  9. package/dist/src/entities/ERC4626Vault.js.map +1 -1
  10. package/dist/src/entities/EVault.d.ts +30 -15
  11. package/dist/src/entities/EVault.d.ts.map +1 -1
  12. package/dist/src/entities/EVault.js +66 -14
  13. package/dist/src/entities/EVault.js.map +1 -1
  14. package/dist/src/entities/EulerEarn.d.ts +3 -0
  15. package/dist/src/entities/EulerEarn.d.ts.map +1 -1
  16. package/dist/src/entities/EulerEarn.js +12 -3
  17. package/dist/src/entities/EulerEarn.js.map +1 -1
  18. package/dist/src/entities/EulerLabels.d.ts +74 -0
  19. package/dist/src/entities/EulerLabels.d.ts.map +1 -1
  20. package/dist/src/entities/Portfolio.d.ts +38 -17
  21. package/dist/src/entities/Portfolio.d.ts.map +1 -1
  22. package/dist/src/entities/Portfolio.js +71 -23
  23. package/dist/src/entities/Portfolio.js.map +1 -1
  24. package/dist/src/entities/Wallet.d.ts +1 -0
  25. package/dist/src/entities/Wallet.d.ts.map +1 -1
  26. package/dist/src/entities/Wallet.js +2 -2
  27. package/dist/src/entities/Wallet.js.map +1 -1
  28. package/dist/src/index.d.ts +5 -2
  29. package/dist/src/index.d.ts.map +1 -1
  30. package/dist/src/index.js +5 -2
  31. package/dist/src/index.js.map +1 -1
  32. package/dist/src/plugins/keyring/keyringPlugin.d.ts +1 -1
  33. package/dist/src/plugins/keyring/keyringPlugin.d.ts.map +1 -1
  34. package/dist/src/plugins/keyring/keyringPlugin.js +70 -12
  35. package/dist/src/plugins/keyring/keyringPlugin.js.map +1 -1
  36. package/dist/src/plugins/pyth/pythPlugin.d.ts.map +1 -1
  37. package/dist/src/plugins/pyth/pythPlugin.js +132 -11
  38. package/dist/src/plugins/pyth/pythPlugin.js.map +1 -1
  39. package/dist/src/plugins/types.d.ts +4 -7
  40. package/dist/src/plugins/types.d.ts.map +1 -1
  41. package/dist/src/plugins/types.js.map +1 -1
  42. package/dist/src/sdk/buildSDK.d.ts +11 -7
  43. package/dist/src/sdk/buildSDK.d.ts.map +1 -1
  44. package/dist/src/sdk/buildSDK.js +369 -116
  45. package/dist/src/sdk/buildSDK.js.map +1 -1
  46. package/dist/src/sdk/config.d.ts +74 -0
  47. package/dist/src/sdk/config.d.ts.map +1 -0
  48. package/dist/src/sdk/config.js +187 -0
  49. package/dist/src/sdk/config.js.map +1 -0
  50. package/dist/src/sdk/defaultConfig.d.ts +5 -2
  51. package/dist/src/sdk/defaultConfig.d.ts.map +1 -1
  52. package/dist/src/sdk/defaultConfig.js +21 -18
  53. package/dist/src/sdk/defaultConfig.js.map +1 -1
  54. package/dist/src/sdk/sdk.d.ts +6 -8
  55. package/dist/src/sdk/sdk.d.ts.map +1 -1
  56. package/dist/src/sdk/sdk.js +2 -6
  57. package/dist/src/sdk/sdk.js.map +1 -1
  58. package/dist/src/services/accountService/accountService.d.ts.map +1 -1
  59. package/dist/src/services/accountService/accountService.js +10 -7
  60. package/dist/src/services/accountService/accountService.js.map +1 -1
  61. package/dist/src/services/accountService/adapters/accountOnchainAdapter/accountInfoConverter.d.ts +2 -2
  62. package/dist/src/services/accountService/adapters/accountOnchainAdapter/accountInfoConverter.d.ts.map +1 -1
  63. package/dist/src/services/accountService/adapters/accountOnchainAdapter/accountInfoConverter.js +22 -17
  64. package/dist/src/services/accountService/adapters/accountOnchainAdapter/accountInfoConverter.js.map +1 -1
  65. package/dist/src/services/accountService/adapters/accountOnchainAdapter/accountOnchainAdapter.d.ts +1 -1
  66. package/dist/src/services/accountService/adapters/accountOnchainAdapter/accountOnchainAdapter.d.ts.map +1 -1
  67. package/dist/src/services/accountService/adapters/accountOnchainAdapter/accountOnchainAdapter.js +16 -17
  68. package/dist/src/services/accountService/adapters/accountOnchainAdapter/accountOnchainAdapter.js.map +1 -1
  69. package/dist/src/services/accountService/adapters/accountV3Adapter/accountV3Adapter.d.ts +7 -1
  70. package/dist/src/services/accountService/adapters/accountV3Adapter/accountV3Adapter.d.ts.map +1 -1
  71. package/dist/src/services/accountService/adapters/accountV3Adapter/accountV3Adapter.js +141 -54
  72. package/dist/src/services/accountService/adapters/accountV3Adapter/accountV3Adapter.js.map +1 -1
  73. package/dist/src/services/deploymentService/deploymentService.d.ts +1 -0
  74. package/dist/src/services/deploymentService/deploymentService.d.ts.map +1 -1
  75. package/dist/src/services/deploymentService/deploymentService.js.map +1 -1
  76. package/dist/src/services/eulerLabelsService/eulerLabelsService.d.ts +18 -1
  77. package/dist/src/services/eulerLabelsService/eulerLabelsService.d.ts.map +1 -1
  78. package/dist/src/services/eulerLabelsService/eulerLabelsService.js +373 -30
  79. package/dist/src/services/eulerLabelsService/eulerLabelsService.js.map +1 -1
  80. package/dist/src/services/executionService/encode.d.ts +30 -0
  81. package/dist/src/services/executionService/encode.d.ts.map +1 -0
  82. package/dist/src/services/executionService/encode.js +841 -0
  83. package/dist/src/services/executionService/encode.js.map +1 -0
  84. package/dist/src/services/executionService/execute.d.ts +65 -0
  85. package/dist/src/services/executionService/execute.d.ts.map +1 -0
  86. package/dist/src/services/executionService/execute.js +185 -0
  87. package/dist/src/services/executionService/execute.js.map +1 -0
  88. package/dist/src/services/executionService/executionService.d.ts +169 -37
  89. package/dist/src/services/executionService/executionService.d.ts.map +1 -1
  90. package/dist/src/services/executionService/executionService.js +510 -893
  91. package/dist/src/services/executionService/executionService.js.map +1 -1
  92. package/dist/src/services/executionService/executionServiceTypes.d.ts +93 -9
  93. package/dist/src/services/executionService/executionServiceTypes.d.ts.map +1 -1
  94. package/dist/src/services/executionService/executionServiceTypes.js +6 -0
  95. package/dist/src/services/executionService/executionServiceTypes.js.map +1 -1
  96. package/dist/src/services/executionService/index.d.ts +5 -2
  97. package/dist/src/services/executionService/index.d.ts.map +1 -1
  98. package/dist/src/services/executionService/index.js +3 -1
  99. package/dist/src/services/executionService/index.js.map +1 -1
  100. package/dist/src/services/{simulationService/simulationService.d.ts → executionService/simulate.d.ts} +26 -40
  101. package/dist/src/services/executionService/simulate.d.ts.map +1 -0
  102. package/dist/src/services/executionService/simulate.js +532 -0
  103. package/dist/src/services/executionService/simulate.js.map +1 -0
  104. package/dist/src/services/intrinsicApyService/adapters/intrinsicApyV3Adapter/intrinsicApyV3AdapterTypes.d.ts +1 -1
  105. package/dist/src/services/intrinsicApyService/adapters/intrinsicApyV3Adapter/intrinsicApyV3AdapterTypes.d.ts.map +1 -1
  106. package/dist/src/services/oracleAdapterService/oracleAdapterService.d.ts +5 -0
  107. package/dist/src/services/oracleAdapterService/oracleAdapterService.d.ts.map +1 -1
  108. package/dist/src/services/oracleAdapterService/oracleAdapterService.js +38 -11
  109. package/dist/src/services/oracleAdapterService/oracleAdapterService.js.map +1 -1
  110. package/dist/src/services/priceService/backendClient.d.ts +7 -13
  111. package/dist/src/services/priceService/backendClient.d.ts.map +1 -1
  112. package/dist/src/services/priceService/backendClient.js +7 -14
  113. package/dist/src/services/priceService/backendClient.js.map +1 -1
  114. package/dist/src/services/priceService/index.d.ts +2 -2
  115. package/dist/src/services/priceService/index.d.ts.map +1 -1
  116. package/dist/src/services/priceService/index.js +1 -1
  117. package/dist/src/services/priceService/index.js.map +1 -1
  118. package/dist/src/services/priceService/priceService.d.ts +19 -17
  119. package/dist/src/services/priceService/priceService.d.ts.map +1 -1
  120. package/dist/src/services/priceService/priceService.js +53 -76
  121. package/dist/src/services/priceService/priceService.js.map +1 -1
  122. package/dist/src/services/rewardsService/adapters/rewardsV3Adapter/rewardsV3Adapter.d.ts.map +1 -1
  123. package/dist/src/services/rewardsService/adapters/rewardsV3Adapter/rewardsV3Adapter.js +26 -13
  124. package/dist/src/services/rewardsService/adapters/rewardsV3Adapter/rewardsV3Adapter.js.map +1 -1
  125. package/dist/src/services/rewardsService/adapters/rewardsV3Adapter/rewardsV3AdapterTypes.d.ts +10 -10
  126. package/dist/src/services/rewardsService/adapters/rewardsV3Adapter/rewardsV3AdapterTypes.d.ts.map +1 -1
  127. package/dist/src/services/rewardsService/rewardsServiceTypes.d.ts +1 -1
  128. package/dist/src/services/rewardsService/rewardsServiceTypes.d.ts.map +1 -1
  129. package/dist/src/services/vaults/eVaultService/adapters/eVaultOnchainAdapter/eVaultOnchainAdapter.d.ts +1 -1
  130. package/dist/src/services/vaults/eVaultService/adapters/eVaultOnchainAdapter/eVaultOnchainAdapter.d.ts.map +1 -1
  131. package/dist/src/services/vaults/eVaultService/adapters/eVaultOnchainAdapter/eVaultOnchainAdapter.js +6 -13
  132. package/dist/src/services/vaults/eVaultService/adapters/eVaultOnchainAdapter/eVaultOnchainAdapter.js.map +1 -1
  133. package/dist/src/services/vaults/eVaultService/adapters/eVaultOnchainAdapter/vaultInfoConverter.d.ts +1 -1
  134. package/dist/src/services/vaults/eVaultService/adapters/eVaultOnchainAdapter/vaultInfoConverter.d.ts.map +1 -1
  135. package/dist/src/services/vaults/eVaultService/adapters/eVaultOnchainAdapter/vaultInfoConverter.js +69 -51
  136. package/dist/src/services/vaults/eVaultService/adapters/eVaultOnchainAdapter/vaultInfoConverter.js.map +1 -1
  137. package/dist/src/services/vaults/eVaultService/adapters/eVaultV3Adapter/eVaultV3Adapter.d.ts +6 -2
  138. package/dist/src/services/vaults/eVaultService/adapters/eVaultV3Adapter/eVaultV3Adapter.d.ts.map +1 -1
  139. package/dist/src/services/vaults/eVaultService/adapters/eVaultV3Adapter/eVaultV3Adapter.js +70 -21
  140. package/dist/src/services/vaults/eVaultService/adapters/eVaultV3Adapter/eVaultV3Adapter.js.map +1 -1
  141. package/dist/src/services/vaults/eVaultService/adapters/eVaultV3Adapter/eVaultV3AdapterConversions.d.ts +1 -1
  142. package/dist/src/services/vaults/eVaultService/adapters/eVaultV3Adapter/eVaultV3AdapterConversions.d.ts.map +1 -1
  143. package/dist/src/services/vaults/eVaultService/adapters/eVaultV3Adapter/eVaultV3AdapterConversions.js +106 -111
  144. package/dist/src/services/vaults/eVaultService/adapters/eVaultV3Adapter/eVaultV3AdapterConversions.js.map +1 -1
  145. package/dist/src/services/vaults/eVaultService/eVaultService.d.ts +4 -4
  146. package/dist/src/services/vaults/eVaultService/eVaultService.d.ts.map +1 -1
  147. package/dist/src/services/vaults/eVaultService/eVaultService.js +56 -57
  148. package/dist/src/services/vaults/eVaultService/eVaultService.js.map +1 -1
  149. package/dist/src/services/vaults/eVaultService/eVaultServiceConfig.d.ts +1 -1
  150. package/dist/src/services/vaults/eVaultService/eVaultServiceConfig.d.ts.map +1 -1
  151. package/dist/src/services/vaults/eulerEarnService/adapters/eulerEarnInfoConverter.d.ts +1 -1
  152. package/dist/src/services/vaults/eulerEarnService/adapters/eulerEarnInfoConverter.d.ts.map +1 -1
  153. package/dist/src/services/vaults/eulerEarnService/adapters/eulerEarnInfoConverter.js +22 -21
  154. package/dist/src/services/vaults/eulerEarnService/adapters/eulerEarnInfoConverter.js.map +1 -1
  155. package/dist/src/services/vaults/eulerEarnService/adapters/eulerEarnOnchainAdapter.d.ts.map +1 -1
  156. package/dist/src/services/vaults/eulerEarnService/adapters/eulerEarnOnchainAdapter.js +27 -32
  157. package/dist/src/services/vaults/eulerEarnService/adapters/eulerEarnOnchainAdapter.js.map +1 -1
  158. package/dist/src/services/vaults/eulerEarnService/adapters/eulerEarnV3Adapter.d.ts +1 -1
  159. package/dist/src/services/vaults/eulerEarnService/adapters/eulerEarnV3Adapter.d.ts.map +1 -1
  160. package/dist/src/services/vaults/eulerEarnService/adapters/eulerEarnV3Adapter.js +113 -72
  161. package/dist/src/services/vaults/eulerEarnService/adapters/eulerEarnV3Adapter.js.map +1 -1
  162. package/dist/src/services/vaults/eulerEarnService/eulerEarnService.d.ts +5 -5
  163. package/dist/src/services/vaults/eulerEarnService/eulerEarnService.d.ts.map +1 -1
  164. package/dist/src/services/vaults/eulerEarnService/eulerEarnService.js +30 -28
  165. package/dist/src/services/vaults/eulerEarnService/eulerEarnService.js.map +1 -1
  166. package/dist/src/services/vaults/eulerEarnService/eulerEarnServiceConfig.d.ts +1 -1
  167. package/dist/src/services/vaults/eulerEarnService/eulerEarnServiceConfig.d.ts.map +1 -1
  168. package/dist/src/services/vaults/securitizeVaultService/adapters/securitizeVaultInfoConverter.d.ts +1 -1
  169. package/dist/src/services/vaults/securitizeVaultService/adapters/securitizeVaultInfoConverter.d.ts.map +1 -1
  170. package/dist/src/services/vaults/securitizeVaultService/adapters/securitizeVaultInfoConverter.js +10 -7
  171. package/dist/src/services/vaults/securitizeVaultService/adapters/securitizeVaultInfoConverter.js.map +1 -1
  172. package/dist/src/services/vaults/securitizeVaultService/adapters/securitizeVaultOnchainAdapter.d.ts.map +1 -1
  173. package/dist/src/services/vaults/securitizeVaultService/adapters/securitizeVaultOnchainAdapter.js +6 -8
  174. package/dist/src/services/vaults/securitizeVaultService/adapters/securitizeVaultOnchainAdapter.js.map +1 -1
  175. package/dist/src/services/vaults/securitizeVaultService/securitizeVaultService.d.ts +2 -2
  176. package/dist/src/services/vaults/securitizeVaultService/securitizeVaultService.d.ts.map +1 -1
  177. package/dist/src/services/vaults/securitizeVaultService/securitizeVaultService.js +13 -18
  178. package/dist/src/services/vaults/securitizeVaultService/securitizeVaultService.js.map +1 -1
  179. package/dist/src/services/vaults/vaultMetaService/adapters/VaultTypeV3Adapter.d.ts +1 -1
  180. package/dist/src/services/vaults/vaultMetaService/adapters/VaultTypeV3Adapter.d.ts.map +1 -1
  181. package/dist/src/services/vaults/vaultMetaService/vaultMetaService.d.ts +3 -3
  182. package/dist/src/services/vaults/vaultMetaService/vaultMetaService.d.ts.map +1 -1
  183. package/dist/src/services/vaults/vaultMetaService/vaultMetaService.js +31 -32
  184. package/dist/src/services/vaults/vaultMetaService/vaultMetaService.js.map +1 -1
  185. package/dist/src/services/walletService/adapters/walletOnchainAdapter.d.ts +5 -1
  186. package/dist/src/services/walletService/adapters/walletOnchainAdapter.d.ts.map +1 -1
  187. package/dist/src/services/walletService/adapters/walletOnchainAdapter.js +181 -40
  188. package/dist/src/services/walletService/adapters/walletOnchainAdapter.js.map +1 -1
  189. package/dist/src/services/walletService/walletService.d.ts +1 -1
  190. package/dist/src/services/walletService/walletService.d.ts.map +1 -1
  191. package/dist/src/services/walletService/walletService.js +4 -3
  192. package/dist/src/services/walletService/walletService.js.map +1 -1
  193. package/dist/src/utils/accountComputations.d.ts +42 -12
  194. package/dist/src/utils/accountComputations.d.ts.map +1 -1
  195. package/dist/src/utils/accountComputations.js +142 -40
  196. package/dist/src/utils/accountComputations.js.map +1 -1
  197. package/dist/src/utils/accountPositionClassification.d.ts +15 -0
  198. package/dist/src/utils/accountPositionClassification.d.ts.map +1 -0
  199. package/dist/src/utils/accountPositionClassification.js +27 -0
  200. package/dist/src/utils/accountPositionClassification.js.map +1 -0
  201. package/dist/src/utils/buildQuery.d.ts +1 -0
  202. package/dist/src/utils/buildQuery.d.ts.map +1 -1
  203. package/dist/src/utils/buildQuery.js +1 -1
  204. package/dist/src/utils/buildQuery.js.map +1 -1
  205. package/dist/src/utils/entityDiagnostics.d.ts +75 -12
  206. package/dist/src/utils/entityDiagnostics.d.ts.map +1 -1
  207. package/dist/src/utils/entityDiagnostics.js +85 -19
  208. package/dist/src/utils/entityDiagnostics.js.map +1 -1
  209. package/dist/src/utils/eulerLabels.d.ts +34 -0
  210. package/dist/src/utils/eulerLabels.d.ts.map +1 -0
  211. package/dist/src/utils/eulerLabels.js +156 -0
  212. package/dist/src/utils/eulerLabels.js.map +1 -0
  213. package/dist/src/utils/healthCheckSets.d.ts +14 -0
  214. package/dist/src/utils/healthCheckSets.d.ts.map +1 -0
  215. package/dist/src/utils/healthCheckSets.js +174 -0
  216. package/dist/src/utils/healthCheckSets.js.map +1 -0
  217. package/dist/src/utils/normalization.d.ts +10 -4
  218. package/dist/src/utils/normalization.d.ts.map +1 -1
  219. package/dist/src/utils/normalization.js +38 -11
  220. package/dist/src/utils/normalization.js.map +1 -1
  221. package/dist/src/utils/oracle.d.ts.map +1 -1
  222. package/dist/src/utils/oracle.js +12 -1
  223. package/dist/src/utils/oracle.js.map +1 -1
  224. package/dist/src/utils/parsing.d.ts +2 -2
  225. package/dist/src/utils/parsing.d.ts.map +1 -1
  226. package/dist/src/utils/parsing.js +12 -22
  227. package/dist/src/utils/parsing.js.map +1 -1
  228. package/package.json +2 -2
  229. package/dist/src/services/simulationService/index.d.ts +0 -3
  230. package/dist/src/services/simulationService/index.d.ts.map +0 -1
  231. package/dist/src/services/simulationService/index.js +0 -2
  232. package/dist/src/services/simulationService/index.js.map +0 -1
  233. package/dist/src/services/simulationService/simulationService.d.ts.map +0 -1
  234. package/dist/src/services/simulationService/simulationService.js +0 -533
  235. package/dist/src/services/simulationService/simulationService.js.map +0 -1
@@ -1,34 +1,137 @@
1
- import { encodeFunctionData, getAddress, maxUint256, zeroAddress, maxUint160, maxUint48, erc20Abi, decodeFunctionData, } from "viem";
1
+ import { decodeFunctionData, encodeFunctionData, erc20Abi, getAddress, maxUint256, maxUint160, } from "viem";
2
+ import { resolveBorrowCollateralPositions } from "../../utils/accountPositionClassification.js";
2
3
  import { ethereumVaultConnectorAbi } from "./abis/ethereumVaultConnectorAbi.js";
3
4
  import { eVaultAbi } from "./abis/eVaultAbi.js";
4
5
  import { permit2PermitAbi } from "./abis/permit2PermitAbi.js";
5
6
  import { swapperAbi } from "./abis/swapperAbi.js";
6
7
  import { swapVerifierAbi } from "./abis/swapVerifierAbi.js";
7
- import { PERMIT2_TYPES, } from "./executionServiceTypes.js";
8
+ import * as encodeHelpers from "./encode.js";
9
+ import { isEVCBatchOperation } from "./executionServiceTypes.js";
10
+ import { executeTransactionPlan, } from "./execute.js";
11
+ import { deriveStateOverrides, estimateGasForTransactionPlan, simulateTransactionPlan, } from "./simulate.js";
8
12
  const TOKENS_REQUIRING_ZERO_APPROVAL_RESET = {
9
13
  1: [getAddress("0xdAC17F958D2ee523a2206206994597C13D831ec7")],
10
14
  };
11
15
  function requiresZeroApprovalReset(chainId, token) {
12
16
  return (TOKENS_REQUIRING_ZERO_APPROVAL_RESET[chainId]?.some((resetToken) => resetToken === getAddress(token)) ?? false);
13
17
  }
14
- const PERMIT2_SIG_WINDOW = 60n * 60n;
18
+ function isSavingsCollateral(collateral) {
19
+ return collateral?.source === "savings";
20
+ }
21
+ function isWalletCollateral(collateral) {
22
+ return !!collateral && !isSavingsCollateral(collateral);
23
+ }
24
+ function cloneBatchEntries(entries) {
25
+ return entries.map((entry) => "type" in entry && entry.type === "operation"
26
+ ? { type: "operation", name: entry.name, items: [...entry.items] }
27
+ : entry);
28
+ }
15
29
  const WAD = 10n ** 18n;
16
30
  // TODO explain how this service is coupled to the concrete abis of ERC4626, permit2 and EVK.
17
31
  // this is a helper service, not a generic one.
18
32
  export class ExecutionService {
19
33
  deploymentService;
20
- walletService;
21
34
  plugins = [];
22
- constructor(deploymentService, walletService) {
35
+ walletService;
36
+ providerService;
37
+ vaultMetaService;
38
+ priceService;
39
+ rewardsService;
40
+ intrinsicApyService;
41
+ eulerLabelsService;
42
+ processPlugins;
43
+ constructor(deploymentService, walletService, providerService, vaultMetaService, priceService, rewardsService, intrinsicApyService, eulerLabelsService) {
23
44
  this.deploymentService = deploymentService;
24
45
  this.walletService = walletService;
46
+ this.providerService = providerService;
47
+ this.vaultMetaService = vaultMetaService;
48
+ this.priceService = priceService;
49
+ this.rewardsService = rewardsService;
50
+ this.intrinsicApyService = intrinsicApyService;
51
+ this.eulerLabelsService = eulerLabelsService;
25
52
  }
26
53
  setWalletService(walletService) {
27
54
  this.walletService = walletService;
28
55
  }
56
+ setProviderService(providerService) {
57
+ this.providerService = providerService;
58
+ }
59
+ setVaultMetaService(vaultMetaService) {
60
+ this.vaultMetaService = vaultMetaService;
61
+ }
62
+ setPriceService(priceService) {
63
+ this.priceService = priceService;
64
+ }
65
+ setRewardsService(rewardsService) {
66
+ this.rewardsService = rewardsService;
67
+ }
68
+ setIntrinsicApyService(intrinsicApyService) {
69
+ this.intrinsicApyService = intrinsicApyService;
70
+ }
71
+ setEulerLabelsService(eulerLabelsService) {
72
+ this.eulerLabelsService = eulerLabelsService;
73
+ }
29
74
  setPlugins(plugins) {
30
75
  this.plugins = plugins;
31
76
  }
77
+ setPluginProcessor(processPlugins) {
78
+ this.processPlugins = processPlugins;
79
+ }
80
+ /** Derive storage overrides needed to simulate the plan against the current account state. */
81
+ async deriveStateOverrides(chainId, account, transactionPlan, options) {
82
+ return deriveStateOverrides(this.getSimulationContext(), chainId, account, transactionPlan, options);
83
+ }
84
+ /** Simulate the full transaction plan, including approval resolution and plugin-aware batch execution. */
85
+ async simulateTransactionPlan(chainId, account, transactionPlan, options) {
86
+ const processedPlan = await this.processPlanPlugins(transactionPlan, account, chainId);
87
+ const owner = this.getAccountOwner(account);
88
+ return simulateTransactionPlan(this.getSimulationContext(), chainId, owner, processedPlan, options);
89
+ }
90
+ /** Estimate gas for the full transaction plan after applying the same simulation pipeline used for execution. */
91
+ async estimateGasForTransactionPlan(chainId, account, transactionPlan, options) {
92
+ const processedPlan = await this.processPlanPlugins(transactionPlan, account, chainId);
93
+ const owner = this.getAccountOwner(account);
94
+ return estimateGasForTransactionPlan(this.getSimulationContext(), chainId, owner, processedPlan, options);
95
+ }
96
+ /** Execute a transaction plan using caller-provided signing and send callbacks. */
97
+ async executeTransactionPlan(args) {
98
+ const { providerService } = this;
99
+ if (!providerService) {
100
+ throw new Error("ExecutionService.executeTransactionPlan requires a providerService. Pass it to the ExecutionService constructor or call setProviderService().");
101
+ }
102
+ const helperArgs = {
103
+ ...args,
104
+ plan: await this.processPlanPlugins(args.plan, args.account, args.chainId),
105
+ executionService: this,
106
+ deploymentService: this.deploymentService,
107
+ providerService,
108
+ };
109
+ return executeTransactionPlan(helperArgs);
110
+ }
111
+ getSimulationContext() {
112
+ return {
113
+ deploymentService: this.deploymentService,
114
+ walletService: this.walletService,
115
+ providerService: this.providerService,
116
+ vaultMetaService: this.vaultMetaService,
117
+ priceService: this.priceService,
118
+ rewardsService: this.rewardsService,
119
+ intrinsicApyService: this.intrinsicApyService,
120
+ eulerLabelsService: this.eulerLabelsService,
121
+ describeBatch: (batch) => this.describeBatch(batch),
122
+ };
123
+ }
124
+ async processPlanPlugins(plan, account, chainId) {
125
+ return this.processPlugins
126
+ ? this.processPlugins(plan, account, chainId)
127
+ : plan;
128
+ }
129
+ getAccountOwner(account) {
130
+ return typeof account === "string" ? account : account.owner;
131
+ }
132
+ getCoreAddresses(chainId) {
133
+ return this.deploymentService.getDeployment(chainId).addresses.coreAddrs;
134
+ }
32
135
  /**
33
136
  * Encodes an array of EVC batch items into a single calldata hex for `EVC.batch()`.
34
137
  *
@@ -36,11 +139,7 @@ export class ExecutionService {
36
139
  * @returns Encoded calldata hex for the EVC batch call
37
140
  */
38
141
  encodeBatch(items) {
39
- return encodeFunctionData({
40
- abi: ethereumVaultConnectorAbi,
41
- functionName: "batch",
42
- args: [items],
43
- });
142
+ return encodeHelpers.encodeBatch(items);
44
143
  }
45
144
  /**
46
145
  * Encodes EVC batch items for depositing underlying assets into a vault (mints shares to receiver).
@@ -55,33 +154,9 @@ export class ExecutionService {
55
154
  * @param args.permit2 - Optional Permit2 message + signature; if set, prepends a permit2 permit call so transferFrom can be used
56
155
  * @returns Array of EVC batch items (optional permit2, optional enableCollateral, deposit)
57
156
  */
58
- encodeDeposit({ chainId, vault, amount, receiver, owner, enableCollateral, permit2, }) {
59
- const items = [];
60
- if (permit2) {
61
- const permit2Call = this.encodePermit2Call({
62
- chainId,
63
- owner,
64
- message: permit2.message,
65
- signature: permit2.signature,
66
- });
67
- items.push(permit2Call);
68
- }
69
- // Add enable collateral if flag is set
70
- if (enableCollateral) {
71
- items.push(this.encodeEnableCollateral(chainId, receiver, vault));
72
- }
73
- // Add deposit operation
74
- items.push({
75
- targetContract: vault,
76
- onBehalfOfAccount: owner,
77
- value: 0n,
78
- data: encodeFunctionData({
79
- abi: eVaultAbi,
80
- functionName: "deposit",
81
- args: [amount, receiver],
82
- }),
83
- });
84
- return items;
157
+ encodeDeposit({ chainId, ...args }) {
158
+ const { evc, permit2 } = this.getCoreAddresses(chainId);
159
+ return encodeHelpers.encodeDeposit(evc, permit2, { chainId, ...args });
85
160
  }
86
161
  /**
87
162
  * Encodes EVC batch items for minting vault shares by depositing underlying assets.
@@ -96,33 +171,9 @@ export class ExecutionService {
96
171
  * @param args.permit2 - Optional Permit2 message + signature for transferFrom
97
172
  * @returns Array of EVC batch items (optional permit2, optional enableCollateral, mint)
98
173
  */
99
- encodeMint({ chainId, vault, shares, receiver, owner, enableCollateral, permit2, }) {
100
- const items = [];
101
- if (permit2) {
102
- const permit2Call = this.encodePermit2Call({
103
- chainId,
104
- owner,
105
- message: permit2.message,
106
- signature: permit2.signature,
107
- });
108
- items.push(permit2Call);
109
- }
110
- // Add enable collateral if flag is set
111
- if (enableCollateral) {
112
- items.push(this.encodeEnableCollateral(chainId, receiver, vault));
113
- }
114
- // Add mint operation
115
- items.push({
116
- targetContract: vault,
117
- onBehalfOfAccount: owner,
118
- value: 0n,
119
- data: encodeFunctionData({
120
- abi: eVaultAbi,
121
- functionName: "mint",
122
- args: [shares, receiver],
123
- }),
124
- });
125
- return items;
174
+ encodeMint({ chainId, ...args }) {
175
+ const { evc, permit2 } = this.getCoreAddresses(chainId);
176
+ return encodeHelpers.encodeMint(evc, permit2, { chainId, ...args });
126
177
  }
127
178
  /**
128
179
  * Encodes EVC batch items for withdrawing underlying assets from a vault (burns shares).
@@ -136,24 +187,9 @@ export class ExecutionService {
136
187
  * @param args.disableCollateral - If true, appends disableCollateral( owner, vault ) via EVC before withdraw
137
188
  * @returns Array of EVC batch items (optional disableCollateral, withdraw)
138
189
  */
139
- encodeWithdraw({ chainId, vault, assets, receiver, owner, disableCollateral, }) {
140
- const items = [];
141
- // Add disable collateral if flag is set
142
- if (disableCollateral) {
143
- items.push(this.encodeDisableCollateral(chainId, owner, vault));
144
- }
145
- // Add withdraw operation
146
- items.push({
147
- targetContract: vault,
148
- onBehalfOfAccount: owner,
149
- value: 0n,
150
- data: encodeFunctionData({
151
- abi: eVaultAbi,
152
- functionName: "withdraw",
153
- args: [assets, receiver, owner],
154
- }),
155
- });
156
- return items;
190
+ encodeWithdraw({ chainId, ...args }) {
191
+ const { evc } = this.getCoreAddresses(chainId);
192
+ return encodeHelpers.encodeWithdraw(evc, { chainId, ...args });
157
193
  }
158
194
  /**
159
195
  * Encodes EVC batch items for redeeming vault shares for underlying assets.
@@ -167,24 +203,9 @@ export class ExecutionService {
167
203
  * @param args.disableCollateral - If true, prepends disableCollateral( owner, vault ) via EVC
168
204
  * @returns Array of EVC batch items (optional disableCollateral, redeem)
169
205
  */
170
- encodeRedeem({ chainId, vault, shares, receiver, owner, disableCollateral, }) {
171
- const items = [];
172
- // Add disable collateral if flag is set
173
- if (disableCollateral) {
174
- items.push(this.encodeDisableCollateral(chainId, owner, vault));
175
- }
176
- // Add redeem operation
177
- items.push({
178
- targetContract: vault,
179
- onBehalfOfAccount: owner,
180
- value: 0n,
181
- data: encodeFunctionData({
182
- abi: eVaultAbi,
183
- functionName: "redeem",
184
- args: [shares, receiver, owner],
185
- }),
186
- });
187
- return items;
206
+ encodeRedeem({ chainId, ...args }) {
207
+ const { evc } = this.getCoreAddresses(chainId);
208
+ return encodeHelpers.encodeRedeem(evc, { chainId, ...args });
188
209
  }
189
210
  /**
190
211
  * Encodes EVC batch items for borrowing from a liability vault, optionally depositing collateral in the same batch.
@@ -205,42 +226,8 @@ export class ExecutionService {
205
226
  * @returns Array of EVC batch items (optional deposit, optional disableController, optional enableController, borrow)
206
227
  */
207
228
  encodeBorrow(args) {
208
- const { chainId, vault, amount, owner, borrowAccount, receiver, enableController = true, currentController, collateralVault, collateralAmount, enableCollateral = true, collateralPermit2, } = args;
209
- const items = [];
210
- // Add collateral if provided
211
- if (collateralVault &&
212
- collateralAmount !== undefined &&
213
- collateralAmount > 0n) {
214
- const depositItems = this.encodeDeposit({
215
- chainId,
216
- vault: collateralVault,
217
- amount: collateralAmount,
218
- receiver: borrowAccount,
219
- enableCollateral,
220
- permit2: collateralPermit2,
221
- owner: owner,
222
- });
223
- items.push(...depositItems);
224
- }
225
- // Add disable controller if there's a different controller enabled
226
- if (currentController && currentController !== vault) {
227
- items.push(this.encodeDisableController(currentController, borrowAccount));
228
- }
229
- if (enableController) {
230
- items.push(this.encodeEnableController(chainId, borrowAccount, vault));
231
- }
232
- // Add borrow operation
233
- items.push({
234
- targetContract: vault,
235
- onBehalfOfAccount: borrowAccount,
236
- value: 0n,
237
- data: encodeFunctionData({
238
- abi: eVaultAbi,
239
- functionName: "borrow",
240
- args: [amount, receiver],
241
- }),
242
- });
243
- return items;
229
+ const { evc, permit2 } = this.getCoreAddresses(args.chainId);
230
+ return encodeHelpers.encodeBorrow(evc, permit2, args);
244
231
  }
245
232
  /**
246
233
  * Encodes EVC batch items for liquidating an undercollateralized account (repay debt, seize collateral).
@@ -257,28 +244,9 @@ export class ExecutionService {
257
244
  * @param args.enableCollateral - If true, enables collateral vault for liquidator sub-account after liquidate (default true)
258
245
  * @returns Array of EVC batch items (optional enableController, liquidate, optional enableCollateral)
259
246
  */
260
- encodeLiquidation({ chainId, vault, violator, collateral, repayAssets, minYieldBalance, liquidatorSubAccountAddress, enableCollateral = true, enableController = true, }) {
261
- const items = [];
262
- // Optionally enable controller for the liquidator account on the liability vault
263
- if (enableController) {
264
- items.push(this.encodeEnableController(chainId, liquidatorSubAccountAddress, vault));
265
- }
266
- // Perform the liquidation
267
- items.push({
268
- targetContract: vault,
269
- onBehalfOfAccount: liquidatorSubAccountAddress,
270
- value: 0n,
271
- data: encodeFunctionData({
272
- abi: eVaultAbi,
273
- functionName: "liquidate",
274
- args: [violator, collateral, repayAssets, minYieldBalance],
275
- }),
276
- });
277
- // Optionally enable collateral for the seized collateral vault on the liquidator account
278
- if (enableCollateral) {
279
- items.push(this.encodeEnableCollateral(chainId, liquidatorSubAccountAddress, collateral));
280
- }
281
- return items;
247
+ encodeLiquidation({ chainId, ...args }) {
248
+ const { evc } = this.getCoreAddresses(chainId);
249
+ return encodeHelpers.encodeLiquidation(evc, { chainId, ...args });
282
250
  }
283
251
  /**
284
252
  * Encodes EVC batch items for pulling debt from one sub-account to another on the same liability vault.
@@ -292,24 +260,9 @@ export class ExecutionService {
292
260
  * @param args.enableController - If true, enables vault as controller for `to` via EVC before pullDebt (default true)
293
261
  * @returns Array of EVC batch items (optional enableController, pullDebt)
294
262
  */
295
- encodePullDebt({ chainId, vault, amount, from, to, enableController = true, }) {
296
- const items = [];
297
- // Add enable controller if flag is set
298
- if (enableController) {
299
- items.push(this.encodeEnableController(chainId, to, vault));
300
- }
301
- // Add pullDebt operation
302
- items.push({
303
- targetContract: vault,
304
- onBehalfOfAccount: to,
305
- value: 0n,
306
- data: encodeFunctionData({
307
- abi: eVaultAbi,
308
- functionName: "pullDebt",
309
- args: [amount, from],
310
- }),
311
- });
312
- return items;
263
+ encodePullDebt({ chainId, ...args }) {
264
+ const { evc } = this.getCoreAddresses(chainId);
265
+ return encodeHelpers.encodePullDebt(evc, { chainId, ...args });
313
266
  }
314
267
  /**
315
268
  * Encodes EVC batch items for a multiply/leverage operation when liability and long asset differ (swap required).
@@ -333,77 +286,8 @@ export class ExecutionService {
333
286
  * @returns Array of EVC batch items
334
287
  */
335
288
  encodeMultiplyWithSwap(args) {
336
- const { chainId, collateralVault, collateralAmount, liabilityVault, liabilityAmount, longVault, owner, receiver, enableCollateral = true, enableCollateralLong = true, currentController, enableController = true, collateralPermit2, swapQuote, } = args;
337
- const items = [];
338
- // 1. Add permit2 for collateral if provided
339
- if (collateralPermit2) {
340
- const permit2Call = this.encodePermit2Call({
341
- chainId,
342
- owner,
343
- message: collateralPermit2.message,
344
- signature: collateralPermit2.signature,
345
- });
346
- items.push(permit2Call);
347
- }
348
- // 2. Deposit initial collateral if amount > 0
349
- if (collateralAmount > 0n) {
350
- // Enable collateral for collateral vault
351
- if (enableCollateral) {
352
- items.push(this.encodeEnableCollateral(chainId, receiver, collateralVault));
353
- }
354
- // Deposit collateral
355
- items.push({
356
- targetContract: collateralVault,
357
- onBehalfOfAccount: owner,
358
- value: 0n,
359
- data: encodeFunctionData({
360
- abi: eVaultAbi,
361
- functionName: "deposit",
362
- args: [collateralAmount, receiver],
363
- }),
364
- });
365
- }
366
- // 3. Disable current controller if there's a different one enabled
367
- if (currentController && currentController !== liabilityVault) {
368
- items.push(this.encodeDisableController(currentController, receiver));
369
- }
370
- // 4. Enable controller for liability vault
371
- if (enableController) {
372
- items.push(this.encodeEnableController(chainId, receiver, liabilityVault));
373
- }
374
- // 5. Borrow from liability vault to swapper
375
- items.push({
376
- targetContract: liabilityVault,
377
- onBehalfOfAccount: receiver,
378
- value: 0n,
379
- data: encodeFunctionData({
380
- abi: eVaultAbi,
381
- functionName: "borrow",
382
- args: [liabilityAmount, swapQuote.swap.swapperAddress],
383
- }),
384
- });
385
- // 6. Execute swap multicall
386
- items.push({
387
- targetContract: swapQuote.swap.swapperAddress,
388
- onBehalfOfAccount: receiver,
389
- value: 0n,
390
- data: swapQuote.swap.swapperData,
391
- });
392
- // 7. Verify swap and skim to long vault
393
- if (swapQuote.verify.type !== "skimMin") {
394
- throw new Error("Invalid swap quote type for multiply - must be skimMin");
395
- }
396
- items.push({
397
- targetContract: swapQuote.verify.verifierAddress,
398
- onBehalfOfAccount: receiver,
399
- value: 0n,
400
- data: swapQuote.verify.verifierData,
401
- });
402
- // 8. Enable collateral on long vault
403
- if (enableCollateralLong && collateralVault !== longVault) {
404
- items.push(this.encodeEnableCollateral(chainId, receiver, longVault));
405
- }
406
- return items;
289
+ const { evc, permit2 } = this.getCoreAddresses(args.chainId);
290
+ return encodeHelpers.encodeMultiplyWithSwap(evc, permit2, args);
407
291
  }
408
292
  /**
409
293
  * Encodes EVC batch items for a multiply/leverage operation when liability and long asset are the same (no swap).
@@ -426,71 +310,8 @@ export class ExecutionService {
426
310
  * @returns Array of EVC batch items
427
311
  */
428
312
  encodeMultiplySameAsset(args) {
429
- const { chainId, collateralVault, collateralAmount, liabilityVault, liabilityAmount, longVault, owner, receiver, enableCollateral = true, enableCollateralLong = true, enableController = true, currentController, collateralPermit2, } = args;
430
- const items = [];
431
- // 1. Add permit2 for collateral if provided
432
- if (collateralPermit2) {
433
- const permit2Call = this.encodePermit2Call({
434
- chainId,
435
- owner,
436
- message: collateralPermit2.message,
437
- signature: collateralPermit2.signature,
438
- });
439
- items.push(permit2Call);
440
- }
441
- // 2. Deposit initial collateral if amount > 0
442
- if (collateralAmount > 0n) {
443
- // Enable collateral for collateral vault
444
- if (enableCollateral) {
445
- items.push(this.encodeEnableCollateral(chainId, receiver, collateralVault));
446
- }
447
- // Deposit collateral
448
- items.push({
449
- targetContract: collateralVault,
450
- onBehalfOfAccount: owner,
451
- value: 0n,
452
- data: encodeFunctionData({
453
- abi: eVaultAbi,
454
- functionName: "deposit",
455
- args: [collateralAmount, receiver],
456
- }),
457
- });
458
- }
459
- // 3. Disable current controller if there's a different one enabled
460
- if (currentController && currentController !== liabilityVault) {
461
- items.push(this.encodeDisableController(currentController, receiver));
462
- }
463
- // 4. Enable controller for liability vault
464
- if (enableController) {
465
- items.push(this.encodeEnableController(chainId, receiver, liabilityVault));
466
- }
467
- // 5. Borrow from liability vault directly to long vault
468
- items.push({
469
- targetContract: liabilityVault,
470
- onBehalfOfAccount: receiver,
471
- value: 0n,
472
- data: encodeFunctionData({
473
- abi: eVaultAbi,
474
- functionName: "borrow",
475
- args: [liabilityAmount, longVault],
476
- }),
477
- });
478
- // 6. Skim borrowed assets to position
479
- items.push({
480
- targetContract: longVault,
481
- onBehalfOfAccount: receiver,
482
- value: 0n,
483
- data: encodeFunctionData({
484
- abi: eVaultAbi,
485
- functionName: "skim",
486
- args: [liabilityAmount, receiver],
487
- }),
488
- });
489
- // 7. Enable collateral on long vault
490
- if (enableCollateralLong) {
491
- items.push(this.encodeEnableCollateral(chainId, receiver, longVault));
492
- }
493
- return items;
313
+ const { evc, permit2 } = this.getCoreAddresses(args.chainId);
314
+ return encodeHelpers.encodeMultiplySameAsset(evc, permit2, args);
494
315
  }
495
316
  /**
496
317
  * Encodes EVC batch items for repaying debt using assets from the sender's wallet (transferFrom to vault then repay).
@@ -507,41 +328,16 @@ export class ExecutionService {
507
328
  * @returns Array of EVC batch items (optional permit2, repay, optional disableController)
508
329
  */
509
330
  encodeRepayFromWallet(args) {
510
- const { chainId, sender, liabilityVault, liabilityAmount, receiver, disableControllerOnMax = true, isMax = false, permit2, } = args;
511
- const items = [];
512
- if (permit2) {
513
- const permit2Call = this.encodePermit2Call({
514
- chainId,
515
- owner: sender,
516
- message: permit2.message,
517
- signature: permit2.signature,
518
- });
519
- items.push(permit2Call);
520
- }
521
- // Repay operation
522
- items.push({
523
- targetContract: liabilityVault,
524
- onBehalfOfAccount: sender,
525
- value: 0n,
526
- data: encodeFunctionData({
527
- abi: eVaultAbi,
528
- functionName: "repay",
529
- args: [isMax ? maxUint256 : liabilityAmount, receiver],
530
- }),
531
- });
532
- // Disable controller if needed (for max repay)
533
- // Sender must be allowed to act on behalf of receiver (sender is subaccount of receiver or is an operator)
534
- if (disableControllerOnMax && isMax) {
535
- items.push(this.encodeDisableController(liabilityVault, receiver));
536
- }
537
- return items;
331
+ const { permit2 } = this.getCoreAddresses(args.chainId);
332
+ return encodeHelpers.encodeRepayFromWallet(permit2, args);
538
333
  }
539
334
  /**
540
335
  * Encodes EVC batch items for repaying debt from a deposit (same-asset only).
541
- * Path 1: same asset and same vault → repayWithShares. Path 2: same asset, different vault → withdraw then repay/skim/repayWithShares as needed.
336
+ * Path 1: same asset and same vault → repayWithShares.
337
+ * Path 2: same asset, different vault → withdraw to liability vault, skim, then repayWithShares.
542
338
  *
543
339
  * @param args - Repay-from-deposit encoding arguments
544
- * @param args.chainId - Chain ID (used for EVC and optional permit2)
340
+ * @param args.chainId - Chain ID used for EVC controller calls
545
341
  * @param args.liabilityVault - Vault (liability) to repay debt to
546
342
  * @param args.liabilityAsset - Underlying asset address of the liability vault
547
343
  * @param args.liabilityAmount - Amount of liability to repay (maxUint256 with isMax for full repay)
@@ -554,31 +350,7 @@ export class ExecutionService {
554
350
  * @returns Array of EVC batch items. Throws if fromAsset !== liabilityAsset.
555
351
  */
556
352
  encodeRepayFromDeposit(args) {
557
- const { chainId, liabilityVault, liabilityAsset, liabilityAmount, from, receiver, fromVault, fromAsset, disableControllerOnMax = true, isMax = false, } = args;
558
- // PATH 1: Same asset, same vault - use repayWithShares
559
- if (fromAsset === liabilityAsset && fromVault === liabilityVault) {
560
- return this.encodeRepayWithSharesSameAssetAndVault({
561
- chainId,
562
- vault: liabilityVault,
563
- amount: liabilityAmount,
564
- receiver,
565
- from,
566
- disableController: isMax && disableControllerOnMax,
567
- });
568
- }
569
- // PATH 2: Same asset, different vault
570
- if (fromAsset === liabilityAsset) {
571
- return this.encodeRepayWithSharesSameAssetDifferentVault({
572
- fromVault,
573
- toVault: liabilityVault,
574
- amount: liabilityAmount,
575
- receiver,
576
- from,
577
- isMax,
578
- disableControllerOnMax,
579
- });
580
- }
581
- throw new Error("encodeRepayFromDeposit only supports same-asset paths");
353
+ return encodeHelpers.encodeRepayFromDeposit(args);
582
354
  }
583
355
  /**
584
356
  * Encodes EVC batch items for repaying debt by swapping collateral (withdraw from vaultIn → swap → verify/repay debtMax).
@@ -593,50 +365,7 @@ export class ExecutionService {
593
365
  * @returns Array of EVC batch items (withdraw, swap, verify/repay, optional disableController)
594
366
  */
595
367
  encodeRepayWithSwap(args) {
596
- const { swapQuote, maxWithdraw, isMax = false, disableControllerOnMax = true, } = args;
597
- const items = [];
598
- // Determine withdraw amount (cap to available amount if provided)
599
- const withdrawAmount = maxWithdraw &&
600
- maxWithdraw < BigInt(swapQuote.amountInMax || swapQuote.amountIn)
601
- ? maxWithdraw
602
- : BigInt(swapQuote.amountInMax || swapQuote.amountIn);
603
- // 1. Withdraw collateral from vault to swapper
604
- items.push({
605
- targetContract: swapQuote.vaultIn,
606
- onBehalfOfAccount: swapQuote.accountIn,
607
- value: 0n,
608
- data: encodeFunctionData({
609
- abi: eVaultAbi,
610
- functionName: "withdraw",
611
- args: [
612
- withdrawAmount,
613
- swapQuote.swap.swapperAddress,
614
- swapQuote.accountIn,
615
- ],
616
- }),
617
- });
618
- // 2. Execute swap multicall
619
- items.push({
620
- targetContract: swapQuote.swap.swapperAddress,
621
- onBehalfOfAccount: swapQuote.accountIn,
622
- value: 0n,
623
- data: swapQuote.swap.swapperData,
624
- });
625
- // 3. Verify swap and repay (verifyDebtMax handles the repay)
626
- if (swapQuote.verify.type !== "debtMax") {
627
- throw new Error("Invalid swap quote type for repay - must be debtMax");
628
- }
629
- items.push({
630
- targetContract: swapQuote.verify.verifierAddress,
631
- onBehalfOfAccount: swapQuote.verify.account,
632
- value: 0n,
633
- data: swapQuote.verify.verifierData,
634
- });
635
- // 4. Disable controller if needed (for max repay)
636
- if (isMax && disableControllerOnMax) {
637
- items.push(this.encodeDisableController(swapQuote.receiver, swapQuote.accountOut));
638
- }
639
- return items;
368
+ return encodeHelpers.encodeRepayWithSwap(args);
640
369
  }
641
370
  /**
642
371
  * Encodes EVC batch items for depositing into a vault using tokens from the user's wallet, going through a swap.
@@ -651,42 +380,8 @@ export class ExecutionService {
651
380
  * @returns Array of EVC batch items (transferFromSender, swap, verify, optional enableCollateral)
652
381
  */
653
382
  encodeDepositWithSwapFromWallet(args) {
654
- const { chainId, swapQuote, amount, sender, enableCollateral = true, } = args;
655
- const items = [];
656
- // 1. Transfer tokens from sender's wallet to swapper via SwapVerifier.transferFromSender
657
- items.push({
658
- targetContract: swapQuote.verify.verifierAddress,
659
- onBehalfOfAccount: sender,
660
- value: 0n,
661
- data: encodeFunctionData({
662
- abi: swapVerifierAbi,
663
- functionName: "transferFromSender",
664
- args: [
665
- swapQuote.tokenIn.address,
666
- amount,
667
- swapQuote.swap.swapperAddress,
668
- ],
669
- }),
670
- });
671
- // 2. Execute swap multicall
672
- items.push({
673
- targetContract: swapQuote.swap.swapperAddress,
674
- onBehalfOfAccount: sender,
675
- value: 0n,
676
- data: swapQuote.swap.swapperData,
677
- });
678
- // 3. Verify swap
679
- items.push({
680
- targetContract: swapQuote.verify.verifierAddress,
681
- onBehalfOfAccount: swapQuote.accountOut || sender,
682
- value: 0n,
683
- data: swapQuote.verify.verifierData,
684
- });
685
- // 4. Enable collateral if needed
686
- if (enableCollateral && swapQuote.receiver) {
687
- items.push(this.encodeEnableCollateral(chainId, swapQuote.accountOut || sender, swapQuote.receiver));
688
- }
689
- return items;
383
+ const { evc } = this.getCoreAddresses(args.chainId);
384
+ return encodeHelpers.encodeDepositWithSwapFromWallet(evc, args);
690
385
  }
691
386
  /**
692
387
  * Encodes EVC batch items for swapping a token from the sender's wallet into another token
@@ -702,38 +397,7 @@ export class ExecutionService {
702
397
  * @returns Array of EVC batch items (transferFromSender, swap, verify)
703
398
  */
704
399
  encodeSwapFromWallet(args) {
705
- const { swapQuote, amount, sender } = args;
706
- const items = [];
707
- if (swapQuote.verify.type !== "transferMin") {
708
- throw new Error("Invalid swap quote type for wallet swap - must be transferMin");
709
- }
710
- items.push({
711
- targetContract: swapQuote.verify.verifierAddress,
712
- onBehalfOfAccount: sender,
713
- value: 0n,
714
- data: encodeFunctionData({
715
- abi: swapVerifierAbi,
716
- functionName: "transferFromSender",
717
- args: [
718
- swapQuote.tokenIn.address,
719
- amount,
720
- swapQuote.swap.swapperAddress,
721
- ],
722
- }),
723
- });
724
- items.push({
725
- targetContract: swapQuote.swap.swapperAddress,
726
- onBehalfOfAccount: sender,
727
- value: 0n,
728
- data: swapQuote.swap.swapperData,
729
- });
730
- items.push({
731
- targetContract: swapQuote.verify.verifierAddress,
732
- onBehalfOfAccount: sender,
733
- value: 0n,
734
- data: swapQuote.verify.verifierData,
735
- });
736
- return items;
400
+ return encodeHelpers.encodeSwapFromWallet(args);
737
401
  }
738
402
  /**
739
403
  * Encodes EVC batch items for swapping collateral: withdraw from vaultIn → swap → verify/skim to receiver; optional enable/disable collateral.
@@ -748,50 +412,8 @@ export class ExecutionService {
748
412
  * @returns Array of EVC batch items (withdraw, swap, verify/skim, optional disableCollateral, optional enableCollateral)
749
413
  */
750
414
  encodeSwapCollateral(args) {
751
- const { chainId, swapQuote, enableCollateral = true, disableCollateralOnMax = true, isMax = false, } = args;
752
- const items = [];
753
- // 1. Withdraw from source vault to swapper
754
- const withdrawAmount = BigInt(swapQuote.amountInMax || swapQuote.amountIn);
755
- items.push({
756
- targetContract: swapQuote.vaultIn,
757
- onBehalfOfAccount: swapQuote.accountIn,
758
- value: 0n,
759
- data: encodeFunctionData({
760
- abi: eVaultAbi,
761
- functionName: "withdraw",
762
- args: [
763
- withdrawAmount,
764
- swapQuote.swap.swapperAddress,
765
- swapQuote.accountIn,
766
- ],
767
- }),
768
- });
769
- // 2. Execute swap multicall
770
- items.push({
771
- targetContract: swapQuote.swap.swapperAddress,
772
- onBehalfOfAccount: swapQuote.accountIn,
773
- value: 0n,
774
- data: swapQuote.swap.swapperData,
775
- });
776
- // 3. Verify swap and skim
777
- if (swapQuote.verify.type !== "skimMin") {
778
- throw new Error("Invalid swap quote type for swap collateral - must be skimMin");
779
- }
780
- items.push({
781
- targetContract: swapQuote.verify.verifierAddress,
782
- onBehalfOfAccount: swapQuote.accountOut,
783
- value: 0n,
784
- data: swapQuote.verify.verifierData,
785
- });
786
- // 4. Disable collateral if needed (for max swap)
787
- if (isMax && disableCollateralOnMax) {
788
- items.push(this.encodeDisableCollateral(chainId, swapQuote.accountIn, swapQuote.vaultIn));
789
- }
790
- // 5. Enable collateral if needed
791
- if (enableCollateral) {
792
- items.push(this.encodeEnableCollateral(chainId, swapQuote.accountOut, swapQuote.receiver));
793
- }
794
- return items;
415
+ const { evc } = this.getCoreAddresses(args.chainId);
416
+ return encodeHelpers.encodeSwapCollateral(evc, args);
795
417
  }
796
418
  /**
797
419
  * Encodes EVC batch items for swapping debt: enableController → borrow from vaultIn → swap → verify/repay (debtMax).
@@ -805,46 +427,51 @@ export class ExecutionService {
805
427
  * @param args.isMax - If true, treats as full debt swap (can trigger disableControllerOnMax)
806
428
  * @returns Array of EVC batch items (optional enableController, borrow, swap, verify/repay, optional disableController)
807
429
  */
808
- encodeSwapDebt({ chainId, swapQuote, enableController = true, disableControllerOnMax = true, isMax = false, }) {
809
- const items = [];
810
- // Enable controller if needed
811
- if (enableController) {
812
- items.push(this.encodeEnableController(chainId, swapQuote.accountOut, swapQuote.vaultIn));
813
- }
814
- // Borrow from source vault
815
- const borrowAmount = BigInt(swapQuote.amountInMax);
816
- items.push({
817
- targetContract: swapQuote.vaultIn,
818
- onBehalfOfAccount: swapQuote.accountIn,
819
- value: 0n,
820
- data: encodeFunctionData({
821
- abi: eVaultAbi,
822
- functionName: "borrow",
823
- args: [borrowAmount, swapQuote.swap.swapperAddress],
824
- }),
825
- });
826
- // Execute swap multicall
827
- items.push({
828
- targetContract: swapQuote.swap.swapperAddress,
829
- onBehalfOfAccount: swapQuote.accountIn,
830
- value: 0n,
831
- data: swapQuote.swap.swapperData,
832
- });
833
- // Verify swap and skim
834
- if (swapQuote.verify.type !== "debtMax") {
835
- throw new Error("Invalid swap quote type for repay - must be debtMax");
836
- }
837
- items.push({
838
- targetContract: swapQuote.verify.verifierAddress,
839
- onBehalfOfAccount: swapQuote.accountOut,
840
- value: 0n,
841
- data: swapQuote.verify.verifierData,
842
- });
843
- // Disable controller if needed (for max swap)
844
- if (isMax && disableControllerOnMax) {
845
- items.push(this.encodeDisableController(swapQuote.receiver, swapQuote.accountIn));
846
- }
847
- return items;
430
+ encodeSwapDebt(args) {
431
+ const { evc } = this.getCoreAddresses(args.chainId);
432
+ return encodeHelpers.encodeSwapDebt(evc, args);
433
+ }
434
+ /**
435
+ * Encodes EVC batch items for migrating a supplied/collateral position between two same-asset vaults.
436
+ * Partial migration uses withdraw(amount, toVault, account) then skim(amount, account).
437
+ * Max migration uses redeem(maxShares || maxUint256, toVault, account) then skim(amount, account).
438
+ *
439
+ * @param args - Same-asset collateral migration encoding arguments
440
+ * @param args.chainId - Chain ID (used for EVC enable/disable collateral)
441
+ * @param args.fromVault - Source vault holding the supplied shares
442
+ * @param args.toVault - Destination vault with the same underlying asset
443
+ * @param args.amount - Asset amount expected to arrive at the destination vault and be skimmed
444
+ * @param args.account - Sub-account that owns the source shares and receives destination shares
445
+ * @param args.isMax - If true, redeems shares instead of withdrawing assets
446
+ * @param args.maxShares - Optional exact share amount for max migration; defaults to maxUint256
447
+ * @param args.enableCollateralTo - If true, enables the destination vault as collateral after skim
448
+ * @param args.disableCollateralFrom - If true, disables the source vault as collateral after enabling the destination
449
+ * @returns Array of EVC batch items
450
+ */
451
+ encodeMigrateSameAssetCollateral(args) {
452
+ const { evc } = this.getCoreAddresses(args.chainId);
453
+ return encodeHelpers.encodeMigrateSameAssetCollateral(evc, args);
454
+ }
455
+ /**
456
+ * Encodes EVC batch items for migrating a full debt position between two same-asset liability vaults.
457
+ * Flow: enable new controller → borrow with interest cushion to old vault → skim → repay old debt with shares
458
+ * → disable old controller → optionally sweep cushion to the new vault → optionally transfer remaining new-vault shares.
459
+ *
460
+ * @param args - Same-asset debt migration encoding arguments
461
+ * @param args.chainId - Chain ID (used for EVC enable controller)
462
+ * @param args.oldLiabilityVault - Existing debt vault to fully repay and disable
463
+ * @param args.newLiabilityVault - New same-asset debt vault to borrow from
464
+ * @param args.amount - Current debt amount before applying the 0.01% interest cushion
465
+ * @param args.account - Sub-account that owns the debt position
466
+ * @param args.enableController - If true, enables the new liability vault as controller first
467
+ * @param args.disableController - If true, disables the old liability vault after repayment
468
+ * @param args.sweepExcess - If true, redeems any old-vault cushion shares back to the new vault and skims them
469
+ * @param args.transferRemainingSharesTo - If set, transfers all new-vault shares from the sub-account to this address
470
+ * @returns Array of EVC batch items
471
+ */
472
+ encodeMigrateSameAssetDebt(args) {
473
+ const { evc } = this.getCoreAddresses(args.chainId);
474
+ return encodeHelpers.encodeMigrateSameAssetDebt(evc, args);
848
475
  }
849
476
  /**
850
477
  * Encodes EVC batch items for transferring vault shares between sub-accounts.
@@ -859,28 +486,9 @@ export class ExecutionService {
859
486
  * @param args.disableCollateralFrom - If true, prepends disableCollateral( from, vault ) via EVC before transfer
860
487
  * @returns Array of EVC batch items (optional disableCollateralFrom, transfer, optional enableCollateralTo)
861
488
  */
862
- encodeTransfer({ chainId, vault, to, amount, from, enableCollateralTo, disableCollateralFrom, }) {
863
- const items = [];
864
- // Add disable collateral from sender if flag is set
865
- if (disableCollateralFrom) {
866
- items.push(this.encodeDisableCollateral(chainId, from, vault));
867
- }
868
- // Add transfer operation
869
- items.push({
870
- targetContract: vault,
871
- onBehalfOfAccount: from,
872
- value: 0n,
873
- data: encodeFunctionData({
874
- abi: eVaultAbi,
875
- functionName: "transfer",
876
- args: [to, amount],
877
- }),
878
- });
879
- // Add enable collateral to receiver if flag is set
880
- if (enableCollateralTo) {
881
- items.push(this.encodeEnableCollateral(chainId, to, vault));
882
- }
883
- return items;
489
+ encodeTransfer({ chainId, ...args }) {
490
+ const { evc } = this.getCoreAddresses(chainId);
491
+ return encodeHelpers.encodeTransfer(evc, { chainId, ...args });
884
492
  }
885
493
  /**
886
494
  * Encodes a single EVC batch item that calls Permit2's `permit` with the given message and signature.
@@ -894,73 +502,23 @@ export class ExecutionService {
894
502
  * @returns Single EVC batch item (targetContract = Permit2, permit call)
895
503
  */
896
504
  encodePermit2Call(args) {
897
- const { chainId, owner, message, signature } = args;
898
- const deployment = this.deploymentService.getDeployment(chainId);
899
- const permit2 = deployment.addresses.coreAddrs.permit2;
900
- return {
901
- targetContract: permit2,
902
- onBehalfOfAccount: owner,
903
- value: 0n,
904
- data: encodeFunctionData({
905
- abi: permit2PermitAbi,
906
- functionName: "permit",
907
- args: [owner, message, signature],
908
- }),
909
- };
505
+ const { permit2 } = this.getCoreAddresses(args.chainId);
506
+ return encodeHelpers.encodePermit2Call(permit2, args);
910
507
  }
911
508
  encodeEnableCollateral(chainId, account, vault) {
912
- const deployment = this.deploymentService.getDeployment(chainId);
913
- const evc = deployment.addresses.coreAddrs.evc;
914
- return {
915
- targetContract: evc,
916
- onBehalfOfAccount: zeroAddress,
917
- value: 0n,
918
- data: encodeFunctionData({
919
- abi: ethereumVaultConnectorAbi,
920
- functionName: "enableCollateral",
921
- args: [account, vault],
922
- }),
923
- };
509
+ const { evc } = this.getCoreAddresses(chainId);
510
+ return encodeHelpers.encodeEnableCollateral(evc, account, vault);
924
511
  }
925
512
  encodeDisableCollateral(chainId, account, vault) {
926
- const deployment = this.deploymentService.getDeployment(chainId);
927
- const evc = deployment.addresses.coreAddrs.evc;
928
- return {
929
- targetContract: evc,
930
- onBehalfOfAccount: zeroAddress,
931
- value: 0n,
932
- data: encodeFunctionData({
933
- abi: ethereumVaultConnectorAbi,
934
- functionName: "disableCollateral",
935
- args: [account, vault],
936
- }),
937
- };
513
+ const { evc } = this.getCoreAddresses(chainId);
514
+ return encodeHelpers.encodeDisableCollateral(evc, account, vault);
938
515
  }
939
516
  encodeEnableController(chainId, account, vault) {
940
- const deployment = this.deploymentService.getDeployment(chainId);
941
- const evc = deployment.addresses.coreAddrs.evc;
942
- return {
943
- targetContract: evc,
944
- onBehalfOfAccount: zeroAddress,
945
- value: 0n,
946
- data: encodeFunctionData({
947
- abi: ethereumVaultConnectorAbi,
948
- functionName: "enableController",
949
- args: [account, vault],
950
- }),
951
- };
517
+ const { evc } = this.getCoreAddresses(chainId);
518
+ return encodeHelpers.encodeEnableController(evc, account, vault);
952
519
  }
953
520
  encodeDisableController(vault, account) {
954
- return {
955
- targetContract: vault,
956
- onBehalfOfAccount: account,
957
- value: 0n,
958
- data: encodeFunctionData({
959
- abi: eVaultAbi,
960
- functionName: "disableController",
961
- args: [],
962
- }),
963
- };
521
+ return encodeHelpers.encodeDisableController(vault, account);
964
522
  }
965
523
  /**
966
524
  * Builds EIP-712 typed data for a Permit2 PermitSingle signature (token approval for a spender).
@@ -977,46 +535,10 @@ export class ExecutionService {
977
535
  * @returns EIP-712 typed data (domain, types, primaryType, message) for signing
978
536
  */
979
537
  getPermit2TypedData(args) {
980
- const nowInSeconds = () => BigInt(Math.floor(Date.now() / 1000));
981
- const { chainId, token, amount, spender, nonce, sigDeadline, expiration } = args;
982
- const deployment = this.deploymentService.getDeployment(chainId);
983
- const permit2 = deployment.addresses.coreAddrs.permit2;
984
- const permitSingle = {
985
- details: {
986
- token,
987
- amount: amount > maxUint160 ? maxUint160 : amount,
988
- expiration: expiration ?? Number(maxUint48),
989
- nonce,
990
- },
991
- spender,
992
- sigDeadline: sigDeadline ?? nowInSeconds() + PERMIT2_SIG_WINDOW,
993
- };
994
- return {
995
- domain: {
996
- name: "Permit2",
997
- chainId,
998
- verifyingContract: permit2,
999
- },
1000
- types: PERMIT2_TYPES,
1001
- primaryType: "PermitSingle",
1002
- message: permitSingle,
1003
- };
538
+ const { permit2 } = this.getCoreAddresses(args.chainId);
539
+ return encodeHelpers.getPermit2TypedData(permit2, args);
1004
540
  }
1005
- /**
1006
- * Decodes EVC batch items into human-readable function names and named arguments.
1007
- * Tries known ABIs (EVC, eVault, Permit2, swapper, swapVerifier) to decode each item's data.
1008
- *
1009
- * @param batch - Array of EVC batch items (targetContract, onBehalfOfAccount, value, data) to decode
1010
- * @param extraAbis - Optional extra ABIs to try first when decoding unknown batch items.
1011
- * @returns Array of decoded items with targetContract, onBehalfOfAccount, functionName, and args (record of param name to value). Throws if any item cannot be decoded.
1012
- * @example
1013
- * const batchItems = executionService.encodeDeposit({ ... });
1014
- * const described = executionService.describeBatch(batchItems);
1015
- * console.log(described[0].functionName); // "deposit"
1016
- * console.log(described[0].args); // { amount: 1000n, receiver: "0x..." }
1017
- */
1018
541
  describeBatch(batch, extraAbis) {
1019
- const decodedBatchItems = [];
1020
542
  const executionDecodeAbis = [
1021
543
  ...(extraAbis ?? []),
1022
544
  ethereumVaultConnectorAbi,
@@ -1025,7 +547,7 @@ export class ExecutionService {
1025
547
  swapperAbi,
1026
548
  swapVerifierAbi,
1027
549
  ];
1028
- for (const item of batch) {
550
+ const decodeBatchItem = (item) => {
1029
551
  let decoded = false;
1030
552
  for (const abi of executionDecodeAbis) {
1031
553
  try {
@@ -1050,14 +572,13 @@ export class ExecutionService {
1050
572
  }
1051
573
  });
1052
574
  }
1053
- decodedBatchItems.push({
575
+ decoded = true;
576
+ return {
1054
577
  targetContract: item.targetContract,
1055
578
  onBehalfOfAccount: item.onBehalfOfAccount,
1056
579
  functionName: decodedData.functionName,
1057
580
  args: namedArgs,
1058
- });
1059
- decoded = true;
1060
- break;
581
+ };
1061
582
  }
1062
583
  catch { }
1063
584
  }
@@ -1069,29 +590,35 @@ export class ExecutionService {
1069
590
  try {
1070
591
  const result = plugin.decodeBatchItem(item);
1071
592
  if (result) {
1072
- decodedBatchItems.push(result);
1073
593
  decoded = true;
1074
- break;
594
+ return result;
1075
595
  }
1076
596
  }
1077
597
  catch { }
1078
598
  }
1079
599
  }
1080
600
  if (!decoded) {
1081
- decodedBatchItems.push({
601
+ return {
1082
602
  targetContract: item.targetContract,
1083
603
  onBehalfOfAccount: item.onBehalfOfAccount,
1084
604
  functionName: "Unknown",
1085
605
  args: {},
1086
- });
606
+ };
1087
607
  }
1088
- }
1089
- return decodedBatchItems;
608
+ throw new Error("unreachable batch item decode state");
609
+ };
610
+ return batch.map((entry) => isEVCBatchOperation(entry)
611
+ ? {
612
+ type: "operation",
613
+ name: entry.name,
614
+ items: entry.items.map(decodeBatchItem),
615
+ }
616
+ : decodeBatchItem(entry));
1090
617
  }
1091
618
  /**
1092
619
  * Merges multiple transaction plans into a single plan.
1093
620
  * Required approvals for the same (token, owner, spender) are summed.
1094
- * Executable items are preserved in order; adjacent EVC batch items are concatenated.
621
+ * Executable items are preserved in order; adjacent EVC batches are concatenated without flattening operation groupings.
1095
622
  * Can be used to construct a transaction queue.
1096
623
  *
1097
624
  * @param plans - Array of transaction plans to merge
@@ -1117,19 +644,37 @@ export class ExecutionService {
1117
644
  else if (item.type === "evcBatch") {
1118
645
  const previous = executableItems[executableItems.length - 1];
1119
646
  if (previous?.type === "evcBatch") {
1120
- previous.items.push(...item.items);
647
+ previous.items.push(...cloneBatchEntries(item.items));
1121
648
  }
1122
649
  else {
1123
- executableItems.push({ type: "evcBatch", items: [...item.items] });
650
+ executableItems.push({
651
+ type: "evcBatch",
652
+ items: cloneBatchEntries(item.items),
653
+ });
1124
654
  }
1125
655
  }
1126
656
  else {
1127
- executableItems.push(item);
657
+ throw new Error("ExecutionService.mergePlans cannot merge contractCall plan items. Merge these plans manually to preserve call boundaries.");
1128
658
  }
1129
659
  }
1130
660
  }
1131
661
  return [...approvalByKey.values(), ...executableItems];
1132
662
  }
663
+ /**
664
+ * Appends a raw batch item to the last EVC batch in the plan, or creates one.
665
+ * Mutates and returns the provided plan.
666
+ */
667
+ addBatchItemToPlan(plan, item) {
668
+ for (let index = plan.length - 1; index >= 0; index--) {
669
+ const planItem = plan[index];
670
+ if (planItem?.type === "evcBatch") {
671
+ planItem.items.push(item);
672
+ return plan;
673
+ }
674
+ }
675
+ plan.push({ type: "evcBatch", items: [item] });
676
+ return plan;
677
+ }
1133
678
  /**
1134
679
  * Converts EVC batch items into a transaction plan.
1135
680
  * Returns a plan with a single evcBatch containing the given items (no required approvals).
@@ -1138,127 +683,50 @@ export class ExecutionService {
1138
683
  * @param items - EVC batch items to wrap in a plan
1139
684
  * @returns Transaction plan containing one evcBatch with the items
1140
685
  */
1141
- convertBatchItemsToPlan(items) {
686
+ convertBatchItemsToPlan(items, operationName) {
1142
687
  if (items.length === 0)
1143
688
  return [];
689
+ if (operationName) {
690
+ return [
691
+ {
692
+ type: "evcBatch",
693
+ items: [{ type: "operation", name: operationName, items }],
694
+ },
695
+ ];
696
+ }
1144
697
  return [{ type: "evcBatch", items }];
1145
698
  }
1146
- /**
1147
- * Encodes batch items for repaying with shares from the same asset and vault
1148
- */
1149
- encodeRepayWithSharesSameAssetAndVault({ chainId: _chainId, vault, amount, from, receiver, disableController, }) {
1150
- const items = [];
1151
- // Repay with shares
1152
- items.push({
1153
- targetContract: vault,
1154
- onBehalfOfAccount: from,
1155
- value: 0n,
1156
- data: encodeFunctionData({
1157
- abi: eVaultAbi,
1158
- functionName: "repayWithShares",
1159
- args: [amount, receiver],
1160
- }),
1161
- });
1162
- // Disable controller if needed (for max repay)
1163
- if (disableController) {
1164
- items.push(this.encodeDisableController(vault, receiver));
1165
- }
1166
- return items;
699
+ encodeTransferFromMax(vault, from, to) {
700
+ return encodeHelpers.encodeTransferFromMax(vault, from, to);
1167
701
  }
1168
702
  /**
1169
- * Encodes batch items for repaying with shares from same asset but different vault
703
+ * Appends post-full-repay cleanup calls owned by plan builders:
704
+ * disable each active collateral used by the repaid borrow, transfer those collateral shares to the owner,
705
+ * and, when a source deposit funded the repay, transfer any remaining source-vault shares to the owner.
1170
706
  */
1171
- encodeRepayWithSharesSameAssetDifferentVault({ fromVault, toVault, amount, // if isMax, this should be the total current debt
1172
- receiver, from, isMax, disableControllerOnMax, }) {
1173
- const items = [];
1174
- if (isMax) {
1175
- // if amount was max uint, skim and repay with shares would not revert if after withdraw funds were skimmed
1176
- // by other party
1177
- if (amount == maxUint256) {
1178
- throw new Error("Amount is maxUint256, cannot be used for max repay");
1179
- }
1180
- // For max repay: withdraw full debt amount +1 BPS to cover interest, then skim, then repayWithShares max
1181
- const amountWithExtra = (amount * 10001n) / 10000n;
1182
- if (amountWithExtra >= maxUint256) {
1183
- throw new Error("Amount with extra exceeds maxUint256");
1184
- }
1185
- // 1. Withdraw from collateral vault
1186
- items.push({
1187
- targetContract: fromVault,
1188
- onBehalfOfAccount: from,
1189
- value: 0n,
1190
- data: encodeFunctionData({
1191
- abi: eVaultAbi,
1192
- functionName: "withdraw",
1193
- args: [amountWithExtra, toVault, from],
1194
- }),
1195
- });
1196
- // 2. Skim exact withdrawal amount to liability vault
1197
- items.push({
1198
- targetContract: toVault,
1199
- onBehalfOfAccount: receiver,
1200
- value: 0n,
1201
- data: encodeFunctionData({
1202
- abi: eVaultAbi,
1203
- functionName: "skim",
1204
- args: [amountWithExtra, receiver],
1205
- }),
1206
- });
1207
- // 3. Repay with shares (max)
1208
- items.push({
1209
- targetContract: toVault,
1210
- onBehalfOfAccount: receiver,
1211
- value: 0n,
1212
- data: encodeFunctionData({
1213
- abi: eVaultAbi,
1214
- functionName: "repayWithShares",
1215
- // max is ok now, because skim deposited exact amount and it is the full debt,
1216
- // so pre-existing balance will not be consumed
1217
- args: [maxUint256, receiver],
1218
- }),
1219
- });
1220
- // 4. Disable controller if needed
1221
- if (disableControllerOnMax) {
1222
- items.push(this.encodeDisableController(toVault, receiver));
707
+ appendMaxRepayCleanup(args) {
708
+ const { account, liabilityPosition, receiver, batchItems, sourceAccount, sourceVault, } = args;
709
+ const receiverSubAccount = typeof account.getSubAccount === "function"
710
+ ? account.getSubAccount(receiver)
711
+ : undefined;
712
+ const activeCollaterals = receiverSubAccount
713
+ ? resolveBorrowCollateralPositions(receiverSubAccount, liabilityPosition)
714
+ : [];
715
+ const transferredPositions = new Set();
716
+ for (const collateral of activeCollaterals) {
717
+ batchItems.push(this.encodeDisableCollateral(account.chainId, receiver, collateral.vaultAddress));
718
+ if (getAddress(receiver) !== getAddress(account.owner)) {
719
+ batchItems.push(this.encodeTransferFromMax(collateral.vaultAddress, receiver, account.owner));
1223
720
  }
721
+ transferredPositions.add(`${getAddress(receiver)}:${getAddress(collateral.vaultAddress)}`);
1224
722
  }
1225
- else {
1226
- // For partial repay: withdraw, then repay exact amount
1227
- // 1. Withdraw from source vault directly to the liability vault.
1228
- items.push({
1229
- targetContract: fromVault,
1230
- onBehalfOfAccount: from,
1231
- value: 0n,
1232
- data: encodeFunctionData({
1233
- abi: eVaultAbi,
1234
- functionName: "withdraw",
1235
- args: [amount, toVault, from],
1236
- }),
1237
- });
1238
- // 2. Skim the received assets into the borrow sub-account.
1239
- items.push({
1240
- targetContract: toVault,
1241
- onBehalfOfAccount: receiver,
1242
- value: 0n,
1243
- data: encodeFunctionData({
1244
- abi: eVaultAbi,
1245
- functionName: "skim",
1246
- args: [amount, receiver],
1247
- }),
1248
- });
1249
- // 3. Burn the skimmed shares to repay debt.
1250
- items.push({
1251
- targetContract: toVault,
1252
- onBehalfOfAccount: receiver,
1253
- value: 0n,
1254
- data: encodeFunctionData({
1255
- abi: eVaultAbi,
1256
- functionName: "repayWithShares",
1257
- args: [amount > 0n ? amount - 1n : 0n, receiver],
1258
- }),
1259
- });
723
+ if (sourceAccount && sourceVault) {
724
+ const sourcePositionKey = `${getAddress(sourceAccount)}:${getAddress(sourceVault)}`;
725
+ if (getAddress(sourceAccount) !== getAddress(account.owner) &&
726
+ !transferredPositions.has(sourcePositionKey)) {
727
+ batchItems.push(this.encodeTransferFromMax(sourceVault, sourceAccount, account.owner));
728
+ }
1260
729
  }
1261
- return items;
1262
730
  }
1263
731
  /**
1264
732
  * Resolves RequiredApproval items in a transaction plan by filling in each item's `resolved` field.
@@ -1381,6 +849,9 @@ export class ExecutionService {
1381
849
  */
1382
850
  async resolveRequiredApprovals(args) {
1383
851
  const { plan, chainId, account, usePermit2 = true, unlimitedApproval = false, } = args;
852
+ if (!this.walletService) {
853
+ throw new Error("ExecutionService.resolveRequiredApprovals requires a walletService. Pass it to the ExecutionService constructor or call setWalletService().");
854
+ }
1384
855
  // Filter transaction plan for only RequiredApproval items
1385
856
  const requiredApprovals = plan.filter((item) => item.type === "requiredApproval");
1386
857
  // Transform RequiredApprovals into AssetWithSpenders
@@ -1445,10 +916,7 @@ export class ExecutionService {
1445
916
  enableCollateral: !isCollateralEnabled && enableCollateral,
1446
917
  // Permit2 is handled separately in the plan
1447
918
  });
1448
- plan.push({
1449
- type: "evcBatch",
1450
- items: batchItems,
1451
- });
919
+ plan.push(...this.convertBatchItemsToPlan(batchItems, "deposit"));
1452
920
  return plan;
1453
921
  }
1454
922
  /**
@@ -1490,10 +958,7 @@ export class ExecutionService {
1490
958
  enableCollateral: !isCollateralEnabled && enableCollateral,
1491
959
  // Permit2 is handled separately in the plan
1492
960
  });
1493
- plan.push({
1494
- type: "evcBatch",
1495
- items: batchItems,
1496
- });
961
+ plan.push(...this.convertBatchItemsToPlan(batchItems, "mint"));
1497
962
  return plan;
1498
963
  }
1499
964
  /**
@@ -1522,10 +987,7 @@ export class ExecutionService {
1522
987
  owner,
1523
988
  disableCollateral: disableCollateral && (!position || position.isCollateral),
1524
989
  });
1525
- plan.push({
1526
- type: "evcBatch",
1527
- items: batchItems,
1528
- });
990
+ plan.push(...this.convertBatchItemsToPlan(batchItems, "withdraw"));
1529
991
  return plan;
1530
992
  }
1531
993
  /**
@@ -1555,10 +1017,7 @@ export class ExecutionService {
1555
1017
  owner,
1556
1018
  disableCollateral: disableCollateral && (!position || position.isCollateral),
1557
1019
  });
1558
- plan.push({
1559
- type: "evcBatch",
1560
- items: batchItems,
1561
- });
1020
+ plan.push(...this.convertBatchItemsToPlan(batchItems, "redeem"));
1562
1021
  return plan;
1563
1022
  }
1564
1023
  /**
@@ -1580,6 +1039,12 @@ export class ExecutionService {
1580
1039
  planBorrow(args) {
1581
1040
  const { vault, amount, receiver, borrowAccount, account, collateral } = args;
1582
1041
  const plan = [];
1042
+ const savingsCollateral = isSavingsCollateral(collateral)
1043
+ ? collateral
1044
+ : undefined;
1045
+ const walletCollateral = isWalletCollateral(collateral)
1046
+ ? collateral
1047
+ : undefined;
1583
1048
  const enableCollateral = collateral && collateral.amount > 0n
1584
1049
  ? !(account?.isCollateralEnabled(borrowAccount, collateral.vault) ??
1585
1050
  false)
@@ -1588,15 +1053,15 @@ export class ExecutionService {
1588
1053
  // Default: controller is not enabled when account/sub-account is not available
1589
1054
  const currentController = account?.getCurrentController(borrowAccount);
1590
1055
  const enableController = !(account?.isControllerEnabled(borrowAccount, vault) ?? false);
1591
- if (collateral && collateral.amount > 0n) {
1056
+ if (walletCollateral && walletCollateral.amount > 0n) {
1592
1057
  // Approval is needed from the account owner (who owns the wallet tokens)
1593
1058
  // Add approval requirement (will be resolved later with Wallet data)
1594
1059
  plan.push({
1595
1060
  type: "requiredApproval",
1596
- token: collateral.asset,
1061
+ token: walletCollateral.asset,
1597
1062
  owner: account.owner,
1598
- spender: collateral.vault,
1599
- amount: collateral.amount,
1063
+ spender: walletCollateral.vault,
1064
+ amount: walletCollateral.amount,
1600
1065
  });
1601
1066
  }
1602
1067
  const batchItems = this.encodeBorrow({
@@ -1610,12 +1075,18 @@ export class ExecutionService {
1610
1075
  currentController: currentController || undefined,
1611
1076
  enableCollateral,
1612
1077
  collateralVault: collateral?.vault,
1613
- collateralAmount: collateral?.amount,
1614
- });
1615
- plan.push({
1616
- type: "evcBatch",
1617
- items: batchItems,
1078
+ collateralAmount: walletCollateral?.amount,
1079
+ collateralShareSource: savingsCollateral
1080
+ ? {
1081
+ from: savingsCollateral.from,
1082
+ shares: savingsCollateral.amount,
1083
+ disableCollateralFrom: savingsCollateral.disableCollateralFrom &&
1084
+ (account?.isCollateralEnabled(savingsCollateral.from, savingsCollateral.vault) ??
1085
+ false),
1086
+ }
1087
+ : undefined,
1618
1088
  });
1089
+ plan.push(...this.convertBatchItemsToPlan(batchItems, "borrow"));
1619
1090
  return plan;
1620
1091
  }
1621
1092
  /**
@@ -1659,10 +1130,7 @@ export class ExecutionService {
1659
1130
  enableController,
1660
1131
  enableCollateral,
1661
1132
  });
1662
- plan.push({
1663
- type: "evcBatch",
1664
- items: batchItems,
1665
- });
1133
+ plan.push(...this.convertBatchItemsToPlan(batchItems, "liquidation"));
1666
1134
  return plan;
1667
1135
  }
1668
1136
  /**
@@ -1674,10 +1142,11 @@ export class ExecutionService {
1674
1142
  * @param args.liabilityAmount - Amount of liability asset to repay (use maxUint256 for "repay all")
1675
1143
  * @param args.receiver - Sub-account address whose debt is being repaid
1676
1144
  * @param args.account - Account entity; used for chainId, owner, and position (to resolve liability asset)
1145
+ * @param args.cleanupOnMax - When true and liabilityAmount is maxUint256, the batch disables active collaterals on the repaid sub-account and transfers their shares to the owner account (default false)
1677
1146
  * @returns Array of transaction plan items (approval + EVC batch)
1678
1147
  */
1679
1148
  planRepayFromWallet(args) {
1680
- const { liabilityVault, liabilityAmount, receiver, account } = args;
1149
+ const { liabilityVault, liabilityAmount, receiver, account, cleanupOnMax = false, } = args;
1681
1150
  const plan = [];
1682
1151
  // Get position to determine asset
1683
1152
  const position = account?.getPosition(receiver, liabilityVault);
@@ -1692,6 +1161,7 @@ export class ExecutionService {
1692
1161
  spender: liabilityVault,
1693
1162
  amount: liabilityAmount,
1694
1163
  });
1164
+ const isMax = liabilityAmount === maxUint256;
1695
1165
  // Build EVC batch items
1696
1166
  const batchItems = this.encodeRepayFromWallet({
1697
1167
  chainId: account.chainId,
@@ -1700,18 +1170,28 @@ export class ExecutionService {
1700
1170
  liabilityAmount,
1701
1171
  receiver,
1702
1172
  disableControllerOnMax: true,
1703
- isMax: liabilityAmount === maxUint256,
1173
+ isMax,
1704
1174
  // Permit2 is handled separately in the plan
1705
1175
  });
1706
- plan.push({
1707
- type: "evcBatch",
1708
- items: batchItems,
1709
- });
1176
+ if (cleanupOnMax && isMax) {
1177
+ this.appendMaxRepayCleanup({
1178
+ account,
1179
+ liabilityPosition: position,
1180
+ receiver,
1181
+ batchItems,
1182
+ });
1183
+ }
1184
+ plan.push(...this.convertBatchItemsToPlan(batchItems, "repayFromWallet"));
1710
1185
  return plan;
1711
1186
  }
1712
1187
  /**
1713
1188
  * Builds a transaction plan for repaying debt using assets from another vault deposit (same asset only).
1714
- * Use `maxUint256` for `liabilityAmount` to repay all available debt or up to the available deposit.
1189
+ * Use `maxUint256` for `liabilityAmount` to perform a full repay.
1190
+ * Full repay can opt into cleanup. For different-vault full repays, cleanup only redeems
1191
+ * liability-vault shares left from the repay cushion when the account snapshot shows no pre-existing liability-vault deposit;
1192
+ * otherwise those shares are preserved in the liability vault instead of being migrated to the source vault.
1193
+ * Cleanup also disables active collaterals on the repaid sub-account, transfers those collateral shares to the owner
1194
+ * account, and transfers any remaining source-vault shares from the source sub-account to the owner account.
1715
1195
  *
1716
1196
  * @param args - Repay-from-deposit plan arguments
1717
1197
  * @param args.liabilityVault - Address of the liability vault (debt is repaid to this vault)
@@ -1720,10 +1200,11 @@ export class ExecutionService {
1720
1200
  * @param args.fromVault - Vault to withdraw assets from (must be same underlying asset as liability for this plan)
1721
1201
  * @param args.fromAccount - Sub-account that holds the deposit in `fromVault`
1722
1202
  * @param args.account - Account entity; used for chainId, owner, and positions (to resolve assets and eligibility)
1723
- * @returns Array of transaction plan items (optional approval + EVC batch). Throws if asset differs between fromVault and liabilityVault; use planRepayWithSwap for cross-asset.
1203
+ * @param args.cleanupOnMax - Whether max repay should append repay-cushion, collateral, and source-share cleanup (default false)
1204
+ * @returns Array of transaction plan items (EVC batch only). Throws if asset differs between fromVault and liabilityVault; use planRepayWithSwap for cross-asset.
1724
1205
  */
1725
1206
  planRepayFromDeposit(args) {
1726
- const { liabilityVault, liabilityAmount, receiver, fromVault, fromAccount, account, } = args;
1207
+ const { liabilityVault, liabilityAmount, receiver, fromVault, fromAccount, account, cleanupOnMax = false, } = args;
1727
1208
  const plan = [];
1728
1209
  // Get positions
1729
1210
  const liabilityPosition = account?.getPosition(receiver, liabilityVault);
@@ -1735,7 +1216,7 @@ export class ExecutionService {
1735
1216
  }
1736
1217
  const liabilityAsset = liabilityPosition.asset;
1737
1218
  const fromAsset = fromPosition.asset;
1738
- // Check if approval is needed (only if different assets and we need to swap/withdraw)
1219
+ // Cross-asset repay requires a swap path; this planner only builds same-asset batches.
1739
1220
  if (fromAsset !== liabilityAsset) {
1740
1221
  // This path requires a swap, which is handled by planRepayWithSwap
1741
1222
  throw new Error("planRepayFromDeposit only supports same-asset paths. Use planRepayWithSwap for different assets.");
@@ -1744,6 +1225,7 @@ export class ExecutionService {
1744
1225
  const amount = isMax && fromVault !== liabilityVault
1745
1226
  ? liabilityPosition.borrowed
1746
1227
  : liabilityAmount;
1228
+ const hasPreExistingLiabilityDeposit = (liabilityPosition.assets ?? 0n) > 0n;
1747
1229
  // Build EVC batch items
1748
1230
  const batchItems = this.encodeRepayFromDeposit({
1749
1231
  chainId: account.chainId,
@@ -1758,10 +1240,41 @@ export class ExecutionService {
1758
1240
  isMax,
1759
1241
  // Permit2 is handled separately in the plan
1760
1242
  });
1761
- plan.push({
1762
- type: "evcBatch",
1763
- items: batchItems,
1764
- });
1243
+ if (cleanupOnMax && isMax) {
1244
+ if (fromVault !== liabilityVault && !hasPreExistingLiabilityDeposit) {
1245
+ // Sweep only the repay cushion. If liability shares existed before this batch,
1246
+ // redeem(max) would also migrate that unrelated deposit into the source vault.
1247
+ batchItems.push({
1248
+ targetContract: liabilityVault,
1249
+ onBehalfOfAccount: receiver,
1250
+ value: 0n,
1251
+ data: encodeFunctionData({
1252
+ abi: eVaultAbi,
1253
+ functionName: "redeem",
1254
+ args: [maxUint256, fromVault, receiver],
1255
+ }),
1256
+ });
1257
+ batchItems.push({
1258
+ targetContract: fromVault,
1259
+ onBehalfOfAccount: fromAccount,
1260
+ value: 0n,
1261
+ data: encodeFunctionData({
1262
+ abi: eVaultAbi,
1263
+ functionName: "skim",
1264
+ args: [maxUint256, fromAccount],
1265
+ }),
1266
+ });
1267
+ }
1268
+ this.appendMaxRepayCleanup({
1269
+ account,
1270
+ liabilityPosition,
1271
+ receiver,
1272
+ batchItems,
1273
+ sourceAccount: fromAccount,
1274
+ sourceVault: fromVault,
1275
+ });
1276
+ }
1277
+ plan.push(...this.convertBatchItemsToPlan(batchItems, "repayFromDeposit"));
1765
1278
  return plan;
1766
1279
  }
1767
1280
  /**
@@ -1771,10 +1284,11 @@ export class ExecutionService {
1771
1284
  * @param args - Repay-with-swap plan arguments
1772
1285
  * @param args.swapQuote - Quote from swap service (e.g. fetchRepayQuotes); defines vaultIn, accountIn, accountOut, receiver, swap and verify steps
1773
1286
  * @param args.account - Account entity; used for chainId and positions (to compute isMax and maxWithdraw)
1287
+ * @param args.cleanupOnMax - When true and the quote repays the full debt, the batch disables active collaterals on the repaid sub-account, transfers their shares to the owner account, and transfers remaining source-vault shares to the owner account (default false)
1774
1288
  * @returns Array of transaction plan items (EVC batch: withdraw, swap, verify/repay). Throws if positions not found or liability is zero.
1775
1289
  */
1776
1290
  planRepayWithSwap(args) {
1777
- const { swapQuote, account } = args;
1291
+ const { swapQuote, account, cleanupOnMax = false } = args;
1778
1292
  const plan = [];
1779
1293
  const liabilityPosition = account?.getPosition(swapQuote.accountOut, swapQuote.receiver);
1780
1294
  const fromPosition = account?.getPosition(swapQuote.accountIn, swapQuote.vaultIn);
@@ -1793,10 +1307,17 @@ export class ExecutionService {
1793
1307
  isMax,
1794
1308
  disableControllerOnMax: true,
1795
1309
  });
1796
- plan.push({
1797
- type: "evcBatch",
1798
- items: batchItems,
1799
- });
1310
+ if (cleanupOnMax && isMax) {
1311
+ this.appendMaxRepayCleanup({
1312
+ account,
1313
+ liabilityPosition,
1314
+ receiver: swapQuote.accountOut,
1315
+ batchItems,
1316
+ sourceAccount: swapQuote.accountIn,
1317
+ sourceVault: swapQuote.vaultIn,
1318
+ });
1319
+ }
1320
+ plan.push(...this.convertBatchItemsToPlan(batchItems, "repayWithSwap"));
1800
1321
  return plan;
1801
1322
  }
1802
1323
  /**
@@ -1838,10 +1359,7 @@ export class ExecutionService {
1838
1359
  sender: account.owner,
1839
1360
  enableCollateral: shouldEnableCollateral,
1840
1361
  });
1841
- plan.push({
1842
- type: "evcBatch",
1843
- items: batchItems,
1844
- });
1362
+ plan.push(...this.convertBatchItemsToPlan(batchItems, "depositWithSwapFromWallet"));
1845
1363
  return plan;
1846
1364
  }
1847
1365
  /**
@@ -1871,10 +1389,7 @@ export class ExecutionService {
1871
1389
  amount,
1872
1390
  sender: account.owner,
1873
1391
  });
1874
- plan.push({
1875
- type: "evcBatch",
1876
- items: batchItems,
1877
- });
1392
+ plan.push(...this.convertBatchItemsToPlan(batchItems, "swapFromWallet"));
1878
1393
  return plan;
1879
1394
  }
1880
1395
  /**
@@ -1906,10 +1421,7 @@ export class ExecutionService {
1906
1421
  disableCollateralOnMax: true,
1907
1422
  isMax,
1908
1423
  });
1909
- plan.push({
1910
- type: "evcBatch",
1911
- items: batchItems,
1912
- });
1424
+ plan.push(...this.convertBatchItemsToPlan(batchItems, "swapCollateral"));
1913
1425
  return plan;
1914
1426
  }
1915
1427
  /**
@@ -1937,10 +1449,103 @@ export class ExecutionService {
1937
1449
  disableControllerOnMax: true,
1938
1450
  isMax,
1939
1451
  });
1940
- plan.push({
1941
- type: "evcBatch",
1942
- items: batchItems,
1452
+ plan.push(...this.convertBatchItemsToPlan(batchItems, "swapDebt"));
1453
+ return plan;
1454
+ }
1455
+ /**
1456
+ * Builds a transaction plan for migrating a supplied/collateral position between two same-asset vaults.
1457
+ * This is the no-swap path for moving shares from one vault to another vault with the same underlying asset.
1458
+ *
1459
+ * @param args - Same-asset collateral migration plan arguments
1460
+ * @param args.fromVault - Source vault holding the supplied shares
1461
+ * @param args.toVault - Destination vault with the same underlying asset
1462
+ * @param args.amount - Asset amount to withdraw/redeem and skim into the destination vault
1463
+ * @param args.positionAccount - Sub-account that owns the source shares and receives destination shares
1464
+ * @param args.fromAsset - Optional source underlying asset; defaults to the account position asset
1465
+ * @param args.toAsset - Destination underlying asset, used to verify this is a same-asset migration
1466
+ * @param args.isMax - If true, redeems shares instead of withdrawing assets
1467
+ * @param args.maxShares - Optional exact share amount for max migration
1468
+ * @param args.enableCollateralTo - Optional override for enabling destination collateral
1469
+ * @param args.disableCollateralFrom - Optional override for disabling source collateral
1470
+ * @returns Array of transaction plan items (EVC batch; no token approvals)
1471
+ */
1472
+ planMigrateSameAssetCollateral(args) {
1473
+ const { account, fromVault, toVault, amount, positionAccount, fromAsset, toAsset, isMax = false, maxShares, enableCollateralTo, disableCollateralFrom, } = args;
1474
+ const plan = [];
1475
+ const sourcePosition = account?.getPosition(positionAccount, fromVault);
1476
+ const resolvedFromAsset = fromAsset ?? sourcePosition?.asset;
1477
+ if (!resolvedFromAsset) {
1478
+ throw new Error(`Position not found. From vault: ${fromVault}, Account: ${positionAccount}. Source asset is required when position is not available.`);
1479
+ }
1480
+ if (getAddress(resolvedFromAsset) !== getAddress(toAsset)) {
1481
+ throw new Error("planMigrateSameAssetCollateral only supports same-asset vault migrations. Use planSwapCollateral for cross-asset migrations.");
1482
+ }
1483
+ const shouldEnableCollateralTo = enableCollateralTo ??
1484
+ !account.isCollateralEnabled(positionAccount, toVault);
1485
+ const shouldDisableCollateralFrom = disableCollateralFrom ??
1486
+ (isMax && account.isCollateralEnabled(positionAccount, fromVault));
1487
+ const batchItems = this.encodeMigrateSameAssetCollateral({
1488
+ chainId: account.chainId,
1489
+ fromVault,
1490
+ toVault,
1491
+ amount,
1492
+ account: positionAccount,
1493
+ isMax,
1494
+ maxShares: maxShares ?? (isMax ? sourcePosition?.shares : undefined),
1495
+ enableCollateralTo: shouldEnableCollateralTo,
1496
+ disableCollateralFrom: shouldDisableCollateralFrom,
1943
1497
  });
1498
+ plan.push(...this.convertBatchItemsToPlan(batchItems, "migrateSameAssetCollateral"));
1499
+ return plan;
1500
+ }
1501
+ /**
1502
+ * Builds a transaction plan for migrating a full same-asset debt position from one liability vault to another.
1503
+ * This is the no-swap path for debt vault migration and repays the old vault with shares after borrowing
1504
+ * slightly more from the new vault to cover interest accrual.
1505
+ *
1506
+ * @param args - Same-asset debt migration plan arguments
1507
+ * @param args.oldLiabilityVault - Existing debt vault to fully repay and disable
1508
+ * @param args.newLiabilityVault - New same-asset debt vault to borrow from
1509
+ * @param args.liabilityAccount - Sub-account that owns the debt position
1510
+ * @param args.liabilityAmount - Current debt amount; defaults to the old-vault borrowed amount in account data
1511
+ * @param args.oldLiabilityAsset - Optional old liability asset; defaults to the old-vault account position asset
1512
+ * @param args.newLiabilityAsset - New liability underlying asset, used to verify this is a same-asset migration
1513
+ * @param args.sweepExcess - Whether to redeem and skim the migration cushion back into the new vault (default true)
1514
+ * @param args.transferRemainingSharesToOwner - Whether to transfer new-vault shares to the owner when liabilityAccount differs from owner (default true)
1515
+ * @returns Array of transaction plan items (EVC batch; no token approvals)
1516
+ */
1517
+ planMigrateSameAssetDebt(args) {
1518
+ const { account, oldLiabilityVault, newLiabilityVault, liabilityAccount, liabilityAmount, oldLiabilityAsset, newLiabilityAsset, sweepExcess = true, transferRemainingSharesToOwner = true, } = args;
1519
+ const plan = [];
1520
+ const oldPosition = account?.getPosition(liabilityAccount, oldLiabilityVault);
1521
+ const resolvedOldAsset = oldLiabilityAsset ?? oldPosition?.asset;
1522
+ if (!resolvedOldAsset) {
1523
+ throw new Error(`Position not found. Old liability vault: ${oldLiabilityVault}, Account: ${liabilityAccount}. Old liability asset is required when position is not available.`);
1524
+ }
1525
+ if (getAddress(resolvedOldAsset) !== getAddress(newLiabilityAsset)) {
1526
+ throw new Error("planMigrateSameAssetDebt only supports same-asset debt migrations. Use planSwapDebt for cross-asset migrations.");
1527
+ }
1528
+ const amount = liabilityAmount ?? oldPosition?.borrowed;
1529
+ if (!amount || amount <= 0n) {
1530
+ throw new Error(`Debt position not found or liability is 0. Old liability vault: ${oldLiabilityVault}, Account: ${liabilityAccount}`);
1531
+ }
1532
+ const enableController = !account.isControllerEnabled(liabilityAccount, newLiabilityVault);
1533
+ const transferRemainingSharesTo = transferRemainingSharesToOwner &&
1534
+ getAddress(liabilityAccount) !== getAddress(account.owner)
1535
+ ? account.owner
1536
+ : undefined;
1537
+ const batchItems = this.encodeMigrateSameAssetDebt({
1538
+ chainId: account.chainId,
1539
+ oldLiabilityVault,
1540
+ newLiabilityVault,
1541
+ amount,
1542
+ account: liabilityAccount,
1543
+ enableController,
1544
+ disableController: true,
1545
+ sweepExcess,
1546
+ transferRemainingSharesTo,
1547
+ });
1548
+ plan.push(...this.convertBatchItemsToPlan(batchItems, "migrateSameAssetDebt"));
1944
1549
  return plan;
1945
1550
  }
1946
1551
  /**
@@ -1971,10 +1576,7 @@ export class ExecutionService {
1971
1576
  disableCollateralFrom: disableCollateralFrom &&
1972
1577
  (account?.isCollateralEnabled(from, vault) ?? false),
1973
1578
  });
1974
- plan.push({
1975
- type: "evcBatch",
1976
- items: batchItems,
1977
- });
1579
+ plan.push(...this.convertBatchItemsToPlan(batchItems, "transfer"));
1978
1580
  return plan;
1979
1581
  }
1980
1582
  /**
@@ -2001,10 +1603,7 @@ export class ExecutionService {
2001
1603
  to,
2002
1604
  enableController,
2003
1605
  });
2004
- plan.push({
2005
- type: "evcBatch",
2006
- items: batchItems,
2007
- });
1606
+ plan.push(...this.convertBatchItemsToPlan(batchItems, "pullDebt"));
2008
1607
  return plan;
2009
1608
  }
2010
1609
  /**
@@ -2020,10 +1619,10 @@ export class ExecutionService {
2020
1619
  * @returns Array of transaction plan items (optional approval + EVC batch). Throws if swapQuote.accountIn !== swapQuote.accountOut.
2021
1620
  */
2022
1621
  planMultiplyWithSwap(args) {
2023
- const { collateralVault, collateralAmount, collateralAsset, account, swapQuote, } = args;
1622
+ const { collateralVault, collateralAmount, collateralAsset, collateralShareSource, account, swapQuote, } = args;
2024
1623
  const plan = [];
2025
1624
  // 1. Check if collateral approval is needed (only if depositing collateral)
2026
- if (collateralAmount > 0n) {
1625
+ if (!collateralShareSource && collateralAmount > 0n) {
2027
1626
  // Add approval requirement (will be resolved later with Wallet data)
2028
1627
  plan.push({
2029
1628
  type: "requiredApproval",
@@ -2041,8 +1640,19 @@ export class ExecutionService {
2041
1640
  const longVault = swapQuote.receiver;
2042
1641
  const liabilityAmount = BigInt(swapQuote.amountIn);
2043
1642
  // 2. Determine if collateral needs to be enabled
2044
- const enableCollateral = collateralAmount > 0n &&
1643
+ const hasCollateralInput = collateralShareSource
1644
+ ? collateralShareSource.shares > 0n
1645
+ : collateralAmount > 0n;
1646
+ const enableCollateral = hasCollateralInput &&
2045
1647
  !(account?.isCollateralEnabled(receiver, collateralVault) ?? false);
1648
+ const resolvedCollateralShareSource = collateralShareSource
1649
+ ? {
1650
+ ...collateralShareSource,
1651
+ disableCollateralFrom: collateralShareSource.disableCollateralFrom &&
1652
+ (account?.isCollateralEnabled(collateralShareSource.from, collateralVault) ??
1653
+ false),
1654
+ }
1655
+ : undefined;
2046
1656
  // 3. Determine if controller needs to be enabled
2047
1657
  const enableController = !(account?.isControllerEnabled(receiver, liabilityVault) ?? false);
2048
1658
  // 4. Get current controller (may need to disable if different)
@@ -2060,13 +1670,11 @@ export class ExecutionService {
2060
1670
  enableCollateral,
2061
1671
  currentController: currentController || undefined,
2062
1672
  enableController,
1673
+ collateralShareSource: resolvedCollateralShareSource,
2063
1674
  swapQuote,
2064
1675
  // Permit2 is handled separately in the plan
2065
1676
  });
2066
- plan.push({
2067
- type: "evcBatch",
2068
- items: batchItems,
2069
- });
1677
+ plan.push(...this.convertBatchItemsToPlan(batchItems, "multiplyWithSwap"));
2070
1678
  return plan;
2071
1679
  }
2072
1680
  /**
@@ -2085,10 +1693,10 @@ export class ExecutionService {
2085
1693
  * @returns Array of transaction plan items (optional approval + EVC batch)
2086
1694
  */
2087
1695
  planMultiplySameAsset(args) {
2088
- const { collateralVault, collateralAmount, collateralAsset, liabilityVault, liabilityAmount, longVault, receiver, account, } = args;
1696
+ const { collateralVault, collateralAmount, collateralAsset, collateralShareSource, liabilityVault, liabilityAmount, longVault, receiver, account, } = args;
2089
1697
  const plan = [];
2090
1698
  // 1. Check if collateral approval is needed (only if depositing collateral)
2091
- if (collateralAmount > 0n) {
1699
+ if (!collateralShareSource && collateralAmount > 0n) {
2092
1700
  // Add approval requirement (will be resolved later with Wallet data)
2093
1701
  plan.push({
2094
1702
  type: "requiredApproval",
@@ -2099,8 +1707,19 @@ export class ExecutionService {
2099
1707
  });
2100
1708
  }
2101
1709
  // 2. Determine if collateral needs to be enabled
2102
- const enableCollateral = collateralAmount > 0n &&
1710
+ const hasCollateralInput = collateralShareSource
1711
+ ? collateralShareSource.shares > 0n
1712
+ : collateralAmount > 0n;
1713
+ const enableCollateral = hasCollateralInput &&
2103
1714
  !(account?.isCollateralEnabled(receiver, collateralVault) ?? false);
1715
+ const resolvedCollateralShareSource = collateralShareSource
1716
+ ? {
1717
+ ...collateralShareSource,
1718
+ disableCollateralFrom: collateralShareSource.disableCollateralFrom &&
1719
+ (account?.isCollateralEnabled(collateralShareSource.from, collateralVault) ??
1720
+ false),
1721
+ }
1722
+ : undefined;
2104
1723
  // 3. Determine if controller needs to be enabled
2105
1724
  const enableController = !(account?.isControllerEnabled(receiver, liabilityVault) ?? false);
2106
1725
  // 4. Get current controller (may need to disable if different)
@@ -2118,12 +1737,10 @@ export class ExecutionService {
2118
1737
  enableCollateral,
2119
1738
  currentController: currentController || undefined,
2120
1739
  enableController,
1740
+ collateralShareSource: resolvedCollateralShareSource,
2121
1741
  // Permit2 is handled separately in the plan
2122
1742
  });
2123
- plan.push({
2124
- type: "evcBatch",
2125
- items: batchItems,
2126
- });
1743
+ plan.push(...this.convertBatchItemsToPlan(batchItems, "multiplySameAsset"));
2127
1744
  return plan;
2128
1745
  }
2129
1746
  }