@eulerxyz/euler-v2-sdk 0.2.0-beta → 0.2.2-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 (111) hide show
  1. package/dist/src/entities/Account.d.ts +2 -2
  2. package/dist/src/entities/Account.d.ts.map +1 -1
  3. package/dist/src/entities/Account.js +2 -2
  4. package/dist/src/entities/Account.js.map +1 -1
  5. package/dist/src/entities/EVault.d.ts.map +1 -1
  6. package/dist/src/entities/EVault.js +3 -9
  7. package/dist/src/entities/EVault.js.map +1 -1
  8. package/dist/src/index.d.ts +1 -1
  9. package/dist/src/index.d.ts.map +1 -1
  10. package/dist/src/index.js +1 -1
  11. package/dist/src/index.js.map +1 -1
  12. package/dist/src/plugins/pyth/pythPlugin.d.ts +7 -1
  13. package/dist/src/plugins/pyth/pythPlugin.d.ts.map +1 -1
  14. package/dist/src/plugins/pyth/pythPlugin.js +85 -9
  15. package/dist/src/plugins/pyth/pythPlugin.js.map +1 -1
  16. package/dist/src/sdk/buildSDK.js +1 -1
  17. package/dist/src/sdk/buildSDK.js.map +1 -1
  18. package/dist/src/sdk/defaultConfig.d.ts +2 -2
  19. package/dist/src/sdk/defaultConfig.d.ts.map +1 -1
  20. package/dist/src/sdk/defaultConfig.js +3 -3
  21. package/dist/src/sdk/defaultConfig.js.map +1 -1
  22. package/dist/src/services/accountService/accountServiceConfig.d.ts +1 -1
  23. package/dist/src/services/accountService/accountServiceConfig.d.ts.map +1 -1
  24. package/dist/src/services/executionService/encode.d.ts +15 -11
  25. package/dist/src/services/executionService/encode.d.ts.map +1 -1
  26. package/dist/src/services/executionService/encode.js +242 -23
  27. package/dist/src/services/executionService/encode.js.map +1 -1
  28. package/dist/src/services/executionService/executionService.d.ts +39 -4
  29. package/dist/src/services/executionService/executionService.d.ts.map +1 -1
  30. package/dist/src/services/executionService/executionService.js +335 -24
  31. package/dist/src/services/executionService/executionService.js.map +1 -1
  32. package/dist/src/services/executionService/executionServiceTypes.d.ts +118 -3
  33. package/dist/src/services/executionService/executionServiceTypes.d.ts.map +1 -1
  34. package/dist/src/services/executionService/executionServiceTypes.js.map +1 -1
  35. package/dist/src/services/executionService/index.d.ts +1 -1
  36. package/dist/src/services/executionService/index.d.ts.map +1 -1
  37. package/dist/src/services/executionService/index.js.map +1 -1
  38. package/dist/src/services/intrinsicApyService/adapters/intrinsicApyV3Adapter/intrinsicApyV3Adapter.d.ts +1 -0
  39. package/dist/src/services/intrinsicApyService/adapters/intrinsicApyV3Adapter/intrinsicApyV3Adapter.d.ts.map +1 -1
  40. package/dist/src/services/intrinsicApyService/adapters/intrinsicApyV3Adapter/intrinsicApyV3Adapter.js +14 -2
  41. package/dist/src/services/intrinsicApyService/adapters/intrinsicApyV3Adapter/intrinsicApyV3Adapter.js.map +1 -1
  42. package/dist/src/services/intrinsicApyService/adapters/intrinsicApyV3Adapter/intrinsicApyV3AdapterTypes.d.ts +1 -1
  43. package/dist/src/services/intrinsicApyService/adapters/intrinsicApyV3Adapter/intrinsicApyV3AdapterTypes.d.ts.map +1 -1
  44. package/dist/src/services/priceService/backendClient.d.ts +1 -0
  45. package/dist/src/services/priceService/backendClient.d.ts.map +1 -1
  46. package/dist/src/services/priceService/backendClient.js +14 -3
  47. package/dist/src/services/priceService/backendClient.js.map +1 -1
  48. package/dist/src/services/priceService/priceService.d.ts +2 -1
  49. package/dist/src/services/priceService/priceService.d.ts.map +1 -1
  50. package/dist/src/services/priceService/priceService.js +8 -25
  51. package/dist/src/services/priceService/priceService.js.map +1 -1
  52. package/dist/src/services/rewardsService/adapters/rewardsDirectAdapter/rewardsDirectAdapter.d.ts +8 -2
  53. package/dist/src/services/rewardsService/adapters/rewardsDirectAdapter/rewardsDirectAdapter.d.ts.map +1 -1
  54. package/dist/src/services/rewardsService/adapters/rewardsDirectAdapter/rewardsDirectAdapter.js +209 -42
  55. package/dist/src/services/rewardsService/adapters/rewardsDirectAdapter/rewardsDirectAdapter.js.map +1 -1
  56. package/dist/src/services/rewardsService/adapters/rewardsV3Adapter/rewardsV3Adapter.d.ts.map +1 -1
  57. package/dist/src/services/rewardsService/adapters/rewardsV3Adapter/rewardsV3Adapter.js +38 -6
  58. package/dist/src/services/rewardsService/adapters/rewardsV3Adapter/rewardsV3Adapter.js.map +1 -1
  59. package/dist/src/services/rewardsService/adapters/rewardsV3Adapter/rewardsV3AdapterTypes.d.ts +15 -0
  60. package/dist/src/services/rewardsService/adapters/rewardsV3Adapter/rewardsV3AdapterTypes.d.ts.map +1 -1
  61. package/dist/src/services/rewardsService/rewardsService.d.ts +13 -2
  62. package/dist/src/services/rewardsService/rewardsService.d.ts.map +1 -1
  63. package/dist/src/services/rewardsService/rewardsService.js +272 -14
  64. package/dist/src/services/rewardsService/rewardsService.js.map +1 -1
  65. package/dist/src/services/rewardsService/rewardsServiceTypes.d.ts +44 -5
  66. package/dist/src/services/rewardsService/rewardsServiceTypes.d.ts.map +1 -1
  67. package/dist/src/services/swapService/swapService.d.ts +5 -7
  68. package/dist/src/services/swapService/swapService.d.ts.map +1 -1
  69. package/dist/src/services/swapService/swapService.js +93 -99
  70. package/dist/src/services/swapService/swapService.js.map +1 -1
  71. package/dist/src/services/swapService/swapVerification.d.ts +28 -0
  72. package/dist/src/services/swapService/swapVerification.d.ts.map +1 -0
  73. package/dist/src/services/swapService/swapVerification.js +139 -0
  74. package/dist/src/services/swapService/swapVerification.js.map +1 -0
  75. package/dist/src/services/tokenlistService/tokenlistService.d.ts +1 -1
  76. package/dist/src/services/tokenlistService/tokenlistService.d.ts.map +1 -1
  77. package/dist/src/services/tokenlistService/tokenlistService.js +51 -7
  78. package/dist/src/services/tokenlistService/tokenlistService.js.map +1 -1
  79. package/dist/src/services/tokenlistService/tokenlistServiceTypes.d.ts +1 -1
  80. package/dist/src/services/vaults/eVaultService/adapters/eVaultV3Adapter/eVaultV3AdapterConversions.d.ts.map +1 -1
  81. package/dist/src/services/vaults/eVaultService/adapters/eVaultV3Adapter/eVaultV3AdapterConversions.js +12 -3
  82. package/dist/src/services/vaults/eVaultService/adapters/eVaultV3Adapter/eVaultV3AdapterConversions.js.map +1 -1
  83. package/dist/src/services/vaults/eVaultService/adapters/eVaultV3Adapter/eVaultV3AdapterTypes.d.ts +1 -1
  84. package/dist/src/services/vaults/eVaultService/adapters/eVaultV3Adapter/eVaultV3AdapterTypes.d.ts.map +1 -1
  85. package/dist/src/services/vaults/eVaultService/eVaultService.d.ts.map +1 -1
  86. package/dist/src/services/vaults/eVaultService/eVaultService.js +5 -9
  87. package/dist/src/services/vaults/eVaultService/eVaultService.js.map +1 -1
  88. package/dist/src/services/vaults/eVaultService/eVaultServiceConfig.d.ts +1 -1
  89. package/dist/src/services/vaults/eVaultService/eVaultServiceConfig.d.ts.map +1 -1
  90. package/dist/src/services/vaults/eulerEarnService/adapters/eulerEarnOnchainAdapter.d.ts +1 -1
  91. package/dist/src/services/vaults/eulerEarnService/eulerEarnServiceConfig.d.ts +1 -1
  92. package/dist/src/services/vaults/eulerEarnService/eulerEarnServiceConfig.d.ts.map +1 -1
  93. package/dist/src/services/vaults/vaultMetaService/adapters/VaultTypeV3Adapter.d.ts +1 -1
  94. package/dist/src/services/vaults/vaultMetaService/adapters/VaultTypeV3Adapter.d.ts.map +1 -1
  95. package/dist/src/services/walletService/adapters/walletOnchainAdapter.d.ts +3 -1
  96. package/dist/src/services/walletService/adapters/walletOnchainAdapter.d.ts.map +1 -1
  97. package/dist/src/services/walletService/adapters/walletOnchainAdapter.js +89 -43
  98. package/dist/src/services/walletService/adapters/walletOnchainAdapter.js.map +1 -1
  99. package/dist/src/utils/buildQuery.d.ts +8 -1
  100. package/dist/src/utils/buildQuery.d.ts.map +1 -1
  101. package/dist/src/utils/buildQuery.js +70 -11
  102. package/dist/src/utils/buildQuery.js.map +1 -1
  103. package/dist/src/utils/eulerLabels.d.ts +1 -0
  104. package/dist/src/utils/eulerLabels.d.ts.map +1 -1
  105. package/dist/src/utils/eulerLabels.js +21 -2
  106. package/dist/src/utils/eulerLabels.js.map +1 -1
  107. package/dist/src/utils/oracle.d.ts +1 -0
  108. package/dist/src/utils/oracle.d.ts.map +1 -1
  109. package/dist/src/utils/oracle.js +36 -0
  110. package/dist/src/utils/oracle.js.map +1 -1
  111. package/package.json +1 -1
@@ -9,6 +9,8 @@ import * as encodeHelpers from "./encode.js";
9
9
  import { isEVCBatchOperation } from "./executionServiceTypes.js";
10
10
  import { executeTransactionPlan, } from "./execute.js";
11
11
  import { deriveStateOverrides, estimateGasForTransactionPlan, simulateTransactionPlan, } from "./simulate.js";
12
+ import { adjustForInterest, getSwapInputAmount, } from "../swapService/swapVerification.js";
13
+ import { SwapperMode } from "../swapService/swapServiceTypes.js";
12
14
  const TOKENS_REQUIRING_ZERO_APPROVAL_RESET = {
13
15
  1: [getAddress("0xdAC17F958D2ee523a2206206994597C13D831ec7")],
14
16
  };
@@ -26,6 +28,124 @@ function cloneBatchEntries(entries) {
26
28
  ? { type: "operation", name: entry.name, items: [...entry.items] }
27
29
  : entry);
28
30
  }
31
+ function hasSuppliedPosition(position) {
32
+ return position.assets > 0n || position.shares > 0n;
33
+ }
34
+ function hasShareConversion(vault) {
35
+ return (typeof vault === "object" &&
36
+ vault !== null &&
37
+ "convertToShares" in vault &&
38
+ typeof vault.convertToShares === "function");
39
+ }
40
+ function getStateTransition(item) {
41
+ try {
42
+ const decoded = decodeFunctionData({
43
+ abi: ethereumVaultConnectorAbi,
44
+ data: item.data,
45
+ });
46
+ if (decoded.functionName === "enableCollateral" ||
47
+ decoded.functionName === "disableCollateral" ||
48
+ decoded.functionName === "enableController") {
49
+ const [account, vault] = decoded.args;
50
+ return {
51
+ kind: decoded.functionName === "enableController"
52
+ ? "controller"
53
+ : "collateral",
54
+ action: decoded.functionName.startsWith("enable")
55
+ ? "enable"
56
+ : "disable",
57
+ account,
58
+ vault,
59
+ };
60
+ }
61
+ }
62
+ catch { }
63
+ try {
64
+ const decoded = decodeFunctionData({
65
+ abi: eVaultAbi,
66
+ data: item.data,
67
+ });
68
+ if (decoded.functionName === "disableController") {
69
+ return {
70
+ kind: "controller",
71
+ action: "disable",
72
+ account: item.onBehalfOfAccount,
73
+ vault: item.targetContract,
74
+ };
75
+ }
76
+ }
77
+ catch { }
78
+ return undefined;
79
+ }
80
+ function stateTransitionKey(transition) {
81
+ return `${transition.kind}:${getAddress(transition.account)}:${getAddress(transition.vault)}`;
82
+ }
83
+ function normalizeEVCStateTransitions(entries) {
84
+ const entryRefs = entries.map((entry) => isEVCBatchOperation(entry)
85
+ ? entry.items.map((item) => ({
86
+ item,
87
+ remove: false,
88
+ transition: getStateTransition(item),
89
+ }))
90
+ : [
91
+ {
92
+ item: entry,
93
+ remove: false,
94
+ transition: getStateTransition(entry),
95
+ },
96
+ ]);
97
+ const refs = entryRefs.flat();
98
+ const collateralStacks = new Map();
99
+ const controllerLast = new Map();
100
+ for (const ref of refs) {
101
+ const transition = ref.transition;
102
+ if (!transition)
103
+ continue;
104
+ const key = stateTransitionKey(transition);
105
+ if (transition.kind === "collateral") {
106
+ const stack = collateralStacks.get(key) ?? [];
107
+ const previous = stack[stack.length - 1];
108
+ if (previous?.action === transition.action) {
109
+ ref.remove = true;
110
+ }
111
+ else if (previous) {
112
+ previous.ref.remove = true;
113
+ ref.remove = true;
114
+ stack.pop();
115
+ }
116
+ else {
117
+ stack.push({ action: transition.action, ref });
118
+ }
119
+ collateralStacks.set(key, stack);
120
+ continue;
121
+ }
122
+ const previous = controllerLast.get(key);
123
+ if (previous?.action === transition.action) {
124
+ ref.remove = true;
125
+ }
126
+ else {
127
+ controllerLast.set(key, { action: transition.action });
128
+ }
129
+ }
130
+ const result = [];
131
+ entries.forEach((entry, index) => {
132
+ const refsForEntry = entryRefs[index] ?? [];
133
+ if (isEVCBatchOperation(entry)) {
134
+ const items = refsForEntry
135
+ .filter((ref) => !ref.remove)
136
+ .map((ref) => ref.item);
137
+ if (items.length > 0) {
138
+ result.push({ type: "operation", name: entry.name, items });
139
+ }
140
+ return;
141
+ }
142
+ const ref = refsForEntry[0];
143
+ if (ref && !ref.remove) {
144
+ result.push(ref.item);
145
+ }
146
+ });
147
+ return result;
148
+ }
29
149
  const WAD = 10n ** 18n;
30
150
  // TODO explain how this service is coupled to the concrete abis of ERC4626, permit2 and EVK.
31
151
  // this is a helper service, not a generic one.
@@ -399,6 +519,19 @@ export class ExecutionService {
399
519
  encodeSwapFromWallet(args) {
400
520
  return encodeHelpers.encodeSwapFromWallet(args);
401
521
  }
522
+ encodeSwapAndBorrowFromWallet(args) {
523
+ const { evc } = this.getCoreAddresses(args.chainId);
524
+ return encodeHelpers.encodeSwapAndBorrowFromWallet(evc, args);
525
+ }
526
+ encodeSwapAndRepayFromWallet(args) {
527
+ return encodeHelpers.encodeSwapAndRepayFromWallet(args);
528
+ }
529
+ encodeWithdrawAndSwap(args) {
530
+ return encodeHelpers.encodeWithdrawAndSwap(args);
531
+ }
532
+ encodeRedeemAndSwap(args) {
533
+ return encodeHelpers.encodeRedeemAndSwap(args);
534
+ }
402
535
  /**
403
536
  * Encodes EVC batch items for swapping collateral: withdraw from vaultIn → swap → verify/skim to receiver; optional enable/disable collateral.
404
537
  * Swap quote should come from swapService.fetchDepositQuote() or match the same structure (verify type skimMin).
@@ -531,7 +664,7 @@ export class ExecutionService {
531
664
  * @param args.spender - Address that will be allowed to transfer the token (e.g. vault or Permit2)
532
665
  * @param args.nonce - Unique nonce for this permit (e.g. from Permit2 nonce(owner, token, spender))
533
666
  * @param args.sigDeadline - Signature deadline (defaults to now + 1 hour if omitted)
534
- * @param args.expiration - Permit expiration (defaults to maxUint48 if omitted)
667
+ * @param args.expiration - Permit expiration (defaults to now + 1 hour if omitted)
535
668
  * @returns EIP-712 typed data (domain, types, primaryType, message) for signing
536
669
  */
537
670
  getPermit2TypedData(args) {
@@ -658,7 +791,13 @@ export class ExecutionService {
658
791
  }
659
792
  }
660
793
  }
661
- return [...approvalByKey.values(), ...executableItems];
794
+ const optimizedExecutableItems = executableItems.flatMap((item) => {
795
+ if (item.type !== "evcBatch")
796
+ return [item];
797
+ const items = normalizeEVCStateTransitions(item.items);
798
+ return items.length > 0 ? [{ type: "evcBatch", items }] : [];
799
+ });
800
+ return [...approvalByKey.values(), ...optimizedExecutableItems];
662
801
  }
663
802
  /**
664
803
  * Appends a raw batch item to the last EVC batch in the plan, or creates one.
@@ -738,11 +877,11 @@ export class ExecutionService {
738
877
  * @param args.chainId - Chain ID (used to resolve Permit2 address when usePermit2 is true)
739
878
  * @param args.wallet - Wallet entity with token balances and allowances (assetForVault, assetForPermit2, etc.)
740
879
  * @param args.usePermit2 - If true, prefer Permit2 path (approve Permit2 + sign PermitSingle) when allowance is insufficient (default true)
741
- * @param args.unlimitedApproval - If true, approval/permit amounts use maxUint256/maxUint160 (default true)
880
+ * @param args.unlimitedApproval - If true, direct approvals and Permit2 signed amounts use maxUint256/maxUint160 (default false). Token approvals to Permit2 use maxUint256.
742
881
  * @returns The same plan array with requiredApproval[].resolved populated (approve and/or permit2 data to sign)
743
882
  */
744
883
  resolveRequiredApprovalsWithWallet(args) {
745
- const { plan, wallet, chainId, usePermit2 = true, unlimitedApproval = true, } = args;
884
+ const { plan, wallet, chainId, usePermit2 = true, unlimitedApproval = false, } = args;
746
885
  const deployment = this.deploymentService.getDeployment(chainId);
747
886
  const permit2 = deployment.addresses.coreAddrs.permit2;
748
887
  for (const item of plan) {
@@ -783,7 +922,7 @@ export class ExecutionService {
783
922
  if (!walletAsset || !allowances) {
784
923
  if (usePermit2) {
785
924
  // Need approval to permit2 and permit2 signature
786
- resolvedItems.push(makeApprove(permit2, unlimitedApproval ? maxUint256 : amount));
925
+ resolvedItems.push(makeApprove(permit2, maxUint256));
787
926
  resolvedItems.push(makePermit2(spender, unlimitedApproval ? maxUint160 : amount));
788
927
  }
789
928
  else {
@@ -810,7 +949,7 @@ export class ExecutionService {
810
949
  }
811
950
  // If assetForPermit2 is insufficient, we need both approval and permit2 signature
812
951
  if (!hasSufficientPermit2Allowance) {
813
- addApproveWithOptionalReset(permit2, unlimitedApproval ? maxUint256 : amount, assetForPermit2);
952
+ addApproveWithOptionalReset(permit2, maxUint256, assetForPermit2);
814
953
  resolvedItems.push(makePermit2(spender, unlimitedApproval ? maxUint160 : amount));
815
954
  }
816
955
  else {
@@ -880,6 +1019,55 @@ export class ExecutionService {
880
1019
  });
881
1020
  }
882
1021
  // ========== Transaction plan functions ==========
1022
+ /**
1023
+ * Builds a cleanup plan matching Lite's stale collateral/controller policy.
1024
+ *
1025
+ * Cleanup is derived from the provided account snapshot:
1026
+ * - no enabled controllers: disable all enabled collaterals
1027
+ * - enabled controllers but no active borrows: disable all collaterals, then controllers
1028
+ * - active borrows: disable only enabled collaterals with no supplied position
1029
+ *
1030
+ * If the sub-account snapshot is unavailable, no cleanup is planned.
1031
+ */
1032
+ planCleanup(args) {
1033
+ const { account, subAccount } = args;
1034
+ if (typeof account.getSubAccount !== "function")
1035
+ return [];
1036
+ const subAccountSnapshot = account.getSubAccount(subAccount);
1037
+ if (!subAccountSnapshot)
1038
+ return [];
1039
+ const collaterals = subAccountSnapshot.enabledCollaterals ?? [];
1040
+ const controllers = subAccountSnapshot.enabledControllers ?? [];
1041
+ if (collaterals.length === 0 && controllers.length === 0)
1042
+ return [];
1043
+ const items = [];
1044
+ const disableAllCollaterals = () => {
1045
+ for (const collateral of collaterals) {
1046
+ items.push(this.encodeDisableCollateral(account.chainId, subAccount, collateral));
1047
+ }
1048
+ };
1049
+ if (controllers.length === 0) {
1050
+ disableAllCollaterals();
1051
+ return this.convertBatchItemsToPlan(items, "cleanup");
1052
+ }
1053
+ const hasActiveBorrow = subAccountSnapshot.positions.some((position) => position.borrowed > 0n);
1054
+ if (!hasActiveBorrow) {
1055
+ disableAllCollaterals();
1056
+ for (const controller of controllers) {
1057
+ items.push(this.encodeDisableController(controller, subAccount));
1058
+ }
1059
+ return this.convertBatchItemsToPlan(items, "cleanup");
1060
+ }
1061
+ const depositedVaults = new Set(subAccountSnapshot.positions
1062
+ .filter(hasSuppliedPosition)
1063
+ .map((position) => getAddress(position.vaultAddress)));
1064
+ for (const collateral of collaterals) {
1065
+ if (depositedVaults.has(getAddress(collateral)))
1066
+ continue;
1067
+ items.push(this.encodeDisableCollateral(account.chainId, subAccount, collateral));
1068
+ }
1069
+ return this.convertBatchItemsToPlan(items, "cleanup");
1070
+ }
883
1071
  /**
884
1072
  * Builds a transaction plan for depositing assets into a vault.
885
1073
  * Use `maxUint256` for `amount` to deposit all available assets from the wallet.
@@ -894,7 +1082,7 @@ export class ExecutionService {
894
1082
  * @returns Array of transaction plan items (required approvals + EVC batch)
895
1083
  */
896
1084
  planDeposit(args) {
897
- const { vault, amount, receiver, account, asset, enableCollateral } = args;
1085
+ const { vault, amount, receiver, account, asset, enableCollateral, wrappedNativeInfo, } = args;
898
1086
  const plan = [];
899
1087
  // Default: collateral is not enabled when account/position is not available
900
1088
  const isCollateralEnabled = account?.isCollateralEnabled(receiver, vault) ?? false;
@@ -914,6 +1102,7 @@ export class ExecutionService {
914
1102
  receiver,
915
1103
  owner: account.owner,
916
1104
  enableCollateral: !isCollateralEnabled && enableCollateral,
1105
+ wrappedNativeInfo,
917
1106
  // Permit2 is handled separately in the plan
918
1107
  });
919
1108
  plan.push(...this.convertBatchItemsToPlan(batchItems, "deposit"));
@@ -933,7 +1122,7 @@ export class ExecutionService {
933
1122
  * @returns Array of transaction plan items (required approvals + EVC batch)
934
1123
  */
935
1124
  planMint(args) {
936
- const { vault, shares, receiver, account, asset, enableCollateral, sharesToAssetsExchangeRateWad, } = args;
1125
+ const { vault, shares, receiver, account, asset, enableCollateral, sharesToAssetsExchangeRateWad, wrappedNativeInfo, } = args;
937
1126
  const plan = [];
938
1127
  // Default: collateral is not enabled when account/position is not available
939
1128
  const isCollateralEnabled = account?.isCollateralEnabled(receiver, vault) ?? false;
@@ -956,6 +1145,7 @@ export class ExecutionService {
956
1145
  receiver,
957
1146
  owner: account.owner,
958
1147
  enableCollateral: !isCollateralEnabled && enableCollateral,
1148
+ wrappedNativeInfo,
959
1149
  // Permit2 is handled separately in the plan
960
1150
  });
961
1151
  plan.push(...this.convertBatchItemsToPlan(batchItems, "mint"));
@@ -992,11 +1182,13 @@ export class ExecutionService {
992
1182
  }
993
1183
  /**
994
1184
  * Builds a transaction plan for redeeming vault shares for underlying assets.
1185
+ * Pass `shares` directly, or pass `assets` to derive shares from the account's populated vault state.
995
1186
  * Use `maxUint256` for `shares` to redeem all available shares.
996
1187
  *
997
1188
  * @param args - Redeem plan arguments
998
1189
  * @param args.vault - Address of the vault to redeem shares from
999
1190
  * @param args.shares - Number of vault shares to redeem (use maxUint256 for "redeem all")
1191
+ * @param args.assets - Underlying asset amount used to calculate shares from the account vault snapshot
1000
1192
  * @param args.owner - Sub-account address whose shares are being redeemed
1001
1193
  * @param args.receiver - Address that will receive the underlying assets
1002
1194
  * @param args.account - Account entity; used for chainId and position/collateral state
@@ -1004,10 +1196,11 @@ export class ExecutionService {
1004
1196
  * @returns Array of transaction plan items (EVC batch; no approvals needed for redeem)
1005
1197
  */
1006
1198
  planRedeem(args) {
1007
- const { vault, shares, receiver, owner, account, disableCollateral = false, } = args;
1199
+ const { vault, receiver, owner, account, disableCollateral = false, } = args;
1008
1200
  const plan = [];
1009
1201
  // Get position to check collateral state
1010
1202
  const position = account?.getPosition(owner, vault);
1203
+ const shares = this.resolveRedeemShares(args, position);
1011
1204
  // Build EVC batch items
1012
1205
  const batchItems = this.encodeRedeem({
1013
1206
  chainId: account.chainId,
@@ -1020,6 +1213,19 @@ export class ExecutionService {
1020
1213
  plan.push(...this.convertBatchItemsToPlan(batchItems, "redeem"));
1021
1214
  return plan;
1022
1215
  }
1216
+ resolveRedeemShares(args, position) {
1217
+ if ("shares" in args && args.shares !== undefined)
1218
+ return args.shares;
1219
+ const vault = position?.vault;
1220
+ if (!hasShareConversion(vault)) {
1221
+ throw new Error("planRedeem with assets requires account position vault state with convertToShares. Populate the account vaults before planning asset-denominated redeem.");
1222
+ }
1223
+ // Asset-denominated redeem intentionally rounds shares down via
1224
+ // convertToShares. Calling withdraw(assets) rounds shares up, which can
1225
+ // burn rounding remainders. The populated vault conversion keeps EVault
1226
+ // virtual deposits in the share calculation.
1227
+ return vault.convertToShares(args.assets);
1228
+ }
1023
1229
  /**
1024
1230
  * Builds a transaction plan for borrowing from a liability vault.
1025
1231
  * Use `maxUint256` for `collateral.amount` to deposit all available collateral asset from the wallet.
@@ -1076,6 +1282,7 @@ export class ExecutionService {
1076
1282
  enableCollateral,
1077
1283
  collateralVault: collateral?.vault,
1078
1284
  collateralAmount: walletCollateral?.amount,
1285
+ collateralWrappedNativeInfo: walletCollateral?.wrappedNativeInfo,
1079
1286
  collateralShareSource: savingsCollateral
1080
1287
  ? {
1081
1288
  from: savingsCollateral.from,
@@ -1153,15 +1360,16 @@ export class ExecutionService {
1153
1360
  if (!position) {
1154
1361
  throw new Error(`Position not found. Liability vault: ${liabilityVault}, Account: ${receiver}`);
1155
1362
  }
1363
+ const isMax = liabilityAmount === maxUint256;
1364
+ const approvalAmount = adjustForInterest(isMax ? position.borrowed : liabilityAmount);
1156
1365
  // Add approval requirement (will be resolved later with Wallet data)
1157
1366
  plan.push({
1158
1367
  type: "requiredApproval",
1159
1368
  token: position.asset,
1160
1369
  owner: account.owner,
1161
1370
  spender: liabilityVault,
1162
- amount: liabilityAmount,
1371
+ amount: approvalAmount,
1163
1372
  });
1164
- const isMax = liabilityAmount === maxUint256;
1165
1373
  // Build EVC batch items
1166
1374
  const batchItems = this.encodeRepayFromWallet({
1167
1375
  chainId: account.chainId,
@@ -1288,7 +1496,7 @@ export class ExecutionService {
1288
1496
  * @returns Array of transaction plan items (EVC batch: withdraw, swap, verify/repay). Throws if positions not found or liability is zero.
1289
1497
  */
1290
1498
  planRepayWithSwap(args) {
1291
- const { swapQuote, account, cleanupOnMax = false } = args;
1499
+ const { swapQuote, account, cleanupOnMax = false, swapperMode } = args;
1292
1500
  const plan = [];
1293
1501
  const liabilityPosition = account?.getPosition(swapQuote.accountOut, swapQuote.receiver);
1294
1502
  const fromPosition = account?.getPosition(swapQuote.accountIn, swapQuote.vaultIn);
@@ -1306,6 +1514,7 @@ export class ExecutionService {
1306
1514
  maxWithdraw,
1307
1515
  isMax,
1308
1516
  disableControllerOnMax: true,
1517
+ swapperMode,
1309
1518
  });
1310
1519
  if (cleanupOnMax && isMax) {
1311
1520
  this.appendMaxRepayCleanup({
@@ -1334,7 +1543,7 @@ export class ExecutionService {
1334
1543
  * @returns Array of transaction plan items (approval to SwapVerifier + EVC batch)
1335
1544
  */
1336
1545
  planDepositWithSwapFromWallet(args) {
1337
- const { swapQuote, amount, tokenIn, account, enableCollateral } = args;
1546
+ const { swapQuote, amount, tokenIn, account, enableCollateral, wrappedNativeInfo, } = args;
1338
1547
  const plan = [];
1339
1548
  // Approval goes to the transferFromSender contract (which uses permit2 transferFrom internally)
1340
1549
  plan.push({
@@ -1358,6 +1567,7 @@ export class ExecutionService {
1358
1567
  amount,
1359
1568
  sender: account.owner,
1360
1569
  enableCollateral: shouldEnableCollateral,
1570
+ wrappedNativeInfo,
1361
1571
  });
1362
1572
  plan.push(...this.convertBatchItemsToPlan(batchItems, "depositWithSwapFromWallet"));
1363
1573
  return plan;
@@ -1374,7 +1584,7 @@ export class ExecutionService {
1374
1584
  * @returns Array of transaction plan items (approval to SwapVerifier + EVC batch)
1375
1585
  */
1376
1586
  planSwapFromWallet(args) {
1377
- const { swapQuote, amount, tokenIn, account } = args;
1587
+ const { swapQuote, amount, tokenIn, account, wrappedNativeInfo } = args;
1378
1588
  const plan = [];
1379
1589
  plan.push({
1380
1590
  type: "requiredApproval",
@@ -1388,10 +1598,102 @@ export class ExecutionService {
1388
1598
  swapQuote,
1389
1599
  amount,
1390
1600
  sender: account.owner,
1601
+ wrappedNativeInfo,
1391
1602
  });
1392
1603
  plan.push(...this.convertBatchItemsToPlan(batchItems, "swapFromWallet"));
1393
1604
  return plan;
1394
1605
  }
1606
+ planSwapAndBorrowFromWallet(args) {
1607
+ const { swapQuote, amount, tokenIn, account, borrowVault, borrowAmount, borrowAccount = swapQuote.accountOut, receiver = account.owner, collateralVault = swapQuote.receiver, wrappedNativeInfo, } = args;
1608
+ const plan = [];
1609
+ plan.push({
1610
+ type: "requiredApproval",
1611
+ token: tokenIn,
1612
+ owner: account.owner,
1613
+ spender: swapQuote.verify.verifierAddress,
1614
+ amount,
1615
+ });
1616
+ const enableController = !(account?.isControllerEnabled(borrowAccount, borrowVault) ?? false);
1617
+ const enableCollateral = !(account?.isCollateralEnabled(borrowAccount, collateralVault) ?? false);
1618
+ const currentController = account?.getCurrentController(borrowAccount);
1619
+ const batchItems = this.encodeSwapAndBorrowFromWallet({
1620
+ chainId: account.chainId,
1621
+ swapQuote,
1622
+ amount,
1623
+ sender: account.owner,
1624
+ borrowAccount,
1625
+ collateralVault,
1626
+ borrowVault,
1627
+ borrowAmount,
1628
+ receiver,
1629
+ currentController: currentController || undefined,
1630
+ enableController,
1631
+ enableCollateral,
1632
+ wrappedNativeInfo,
1633
+ });
1634
+ plan.push(...this.convertBatchItemsToPlan(batchItems, "swapAndBorrowFromWallet"));
1635
+ return plan;
1636
+ }
1637
+ planSwapAndRepayFromWallet(args) {
1638
+ const { swapQuote, amount, tokenIn, account, liabilityVault = swapQuote.receiver, repayAccount = swapQuote.accountOut, isMax, cleanupOnMax = false, wrappedNativeInfo, } = args;
1639
+ const plan = [];
1640
+ plan.push({
1641
+ type: "requiredApproval",
1642
+ token: tokenIn,
1643
+ owner: account.owner,
1644
+ spender: swapQuote.verify.verifierAddress,
1645
+ amount,
1646
+ });
1647
+ const liabilityPosition = account?.getPosition(repayAccount, liabilityVault);
1648
+ const resolvedIsMax = isMax ??
1649
+ (liabilityPosition !== undefined &&
1650
+ liabilityPosition.borrowed <= BigInt(swapQuote.amountOutMin || 0));
1651
+ const batchItems = this.encodeSwapAndRepayFromWallet({
1652
+ chainId: account.chainId,
1653
+ swapQuote,
1654
+ amount,
1655
+ sender: account.owner,
1656
+ liabilityVault,
1657
+ repayAccount,
1658
+ isMax: resolvedIsMax,
1659
+ disableControllerOnMax: true,
1660
+ wrappedNativeInfo,
1661
+ });
1662
+ if (cleanupOnMax && resolvedIsMax && liabilityPosition) {
1663
+ this.appendMaxRepayCleanup({
1664
+ account,
1665
+ liabilityPosition,
1666
+ receiver: repayAccount,
1667
+ batchItems,
1668
+ });
1669
+ }
1670
+ plan.push(...this.convertBatchItemsToPlan(batchItems, "swapAndRepayFromWallet"));
1671
+ return plan;
1672
+ }
1673
+ planWithdrawAndSwap(args) {
1674
+ const { account, vault, assets, owner, swapQuote } = args;
1675
+ const batchItems = this.encodeWithdrawAndSwap({
1676
+ chainId: account.chainId,
1677
+ vault,
1678
+ assets,
1679
+ owner,
1680
+ sender: account.owner,
1681
+ swapQuote,
1682
+ });
1683
+ return this.convertBatchItemsToPlan(batchItems, "withdrawAndSwap");
1684
+ }
1685
+ planRedeemAndSwap(args) {
1686
+ const { account, vault, shares, owner, swapQuote } = args;
1687
+ const batchItems = this.encodeRedeemAndSwap({
1688
+ chainId: account.chainId,
1689
+ vault,
1690
+ shares,
1691
+ owner,
1692
+ sender: account.owner,
1693
+ swapQuote,
1694
+ });
1695
+ return this.convertBatchItemsToPlan(batchItems, "redeemAndSwap");
1696
+ }
1395
1697
  /**
1396
1698
  * Builds a transaction plan for swapping collateral from one vault to another (withdraw → swap → deposit/skim).
1397
1699
  *
@@ -1401,7 +1703,7 @@ export class ExecutionService {
1401
1703
  * @returns Array of transaction plan items (EVC batch: withdraw, swap, verify/skim, optional enable/disable collateral)
1402
1704
  */
1403
1705
  planSwapCollateral(args) {
1404
- const { swapQuote, account } = args;
1706
+ const { swapQuote, account, swapperMode } = args;
1405
1707
  const plan = [];
1406
1708
  // Check if source collateral needs to be disabled (when all is swapped)
1407
1709
  // Default: when position is not available, assume amounts are zero, so we don't disable collateral
@@ -1420,6 +1722,7 @@ export class ExecutionService {
1420
1722
  enableCollateral,
1421
1723
  disableCollateralOnMax: true,
1422
1724
  isMax,
1725
+ swapperMode,
1423
1726
  });
1424
1727
  plan.push(...this.convertBatchItemsToPlan(batchItems, "swapCollateral"));
1425
1728
  return plan;
@@ -1433,11 +1736,11 @@ export class ExecutionService {
1433
1736
  * @returns Array of transaction plan items (EVC batch: enableController, borrow, swap, verify/repay, optional disableController)
1434
1737
  */
1435
1738
  planSwapDebt(args) {
1436
- const { swapQuote, account } = args;
1739
+ const { swapQuote, account, swapperMode } = args;
1437
1740
  const plan = [];
1438
- const sourcePosition = account?.getPosition(swapQuote.accountIn, swapQuote.vaultIn);
1439
- const isMax = sourcePosition
1440
- ? sourcePosition.borrowed <= BigInt(swapQuote.amountOutMin)
1741
+ const liabilityPosition = account?.getPosition(swapQuote.accountIn, swapQuote.receiver);
1742
+ const isMax = liabilityPosition
1743
+ ? liabilityPosition.borrowed <= BigInt(swapQuote.amountOutMin)
1441
1744
  : false;
1442
1745
  const enableController = !(account?.isControllerEnabled(swapQuote.accountOut, swapQuote.vaultIn) ??
1443
1746
  false);
@@ -1448,6 +1751,7 @@ export class ExecutionService {
1448
1751
  enableController,
1449
1752
  disableControllerOnMax: true,
1450
1753
  isMax,
1754
+ swapperMode,
1451
1755
  });
1452
1756
  plan.push(...this.convertBatchItemsToPlan(batchItems, "swapDebt"));
1453
1757
  return plan;
@@ -1510,7 +1814,7 @@ export class ExecutionService {
1510
1814
  * @param args.liabilityAmount - Current debt amount; defaults to the old-vault borrowed amount in account data
1511
1815
  * @param args.oldLiabilityAsset - Optional old liability asset; defaults to the old-vault account position asset
1512
1816
  * @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)
1817
+ * @param args.sweepExcess - Whether to redeem and skim the migration cushion back into the new vault when the old vault has no pre-existing supplied shares (default true)
1514
1818
  * @param args.transferRemainingSharesToOwner - Whether to transfer new-vault shares to the owner when liabilityAccount differs from owner (default true)
1515
1819
  * @returns Array of transaction plan items (EVC batch; no token approvals)
1516
1820
  */
@@ -1534,6 +1838,7 @@ export class ExecutionService {
1534
1838
  getAddress(liabilityAccount) !== getAddress(account.owner)
1535
1839
  ? account.owner
1536
1840
  : undefined;
1841
+ const shouldSweepExcess = sweepExcess && !(oldPosition && hasSuppliedPosition(oldPosition));
1537
1842
  const batchItems = this.encodeMigrateSameAssetDebt({
1538
1843
  chainId: account.chainId,
1539
1844
  oldLiabilityVault,
@@ -1542,7 +1847,7 @@ export class ExecutionService {
1542
1847
  account: liabilityAccount,
1543
1848
  enableController,
1544
1849
  disableController: true,
1545
- sweepExcess,
1850
+ sweepExcess: shouldSweepExcess,
1546
1851
  transferRemainingSharesTo,
1547
1852
  });
1548
1853
  plan.push(...this.convertBatchItemsToPlan(batchItems, "migrateSameAssetDebt"));
@@ -1619,7 +1924,7 @@ export class ExecutionService {
1619
1924
  * @returns Array of transaction plan items (optional approval + EVC batch). Throws if swapQuote.accountIn !== swapQuote.accountOut.
1620
1925
  */
1621
1926
  planMultiplyWithSwap(args) {
1622
- const { collateralVault, collateralAmount, collateralAsset, collateralShareSource, account, swapQuote, } = args;
1927
+ const { collateralVault, collateralAmount, collateralAsset, collateralShareSource, collateralWrappedNativeInfo, account, swapQuote, swapperMode, } = args;
1623
1928
  const plan = [];
1624
1929
  // 1. Check if collateral approval is needed (only if depositing collateral)
1625
1930
  if (!collateralShareSource && collateralAmount > 0n) {
@@ -1638,13 +1943,14 @@ export class ExecutionService {
1638
1943
  const receiver = swapQuote.accountIn;
1639
1944
  const liabilityVault = swapQuote.vaultIn;
1640
1945
  const longVault = swapQuote.receiver;
1641
- const liabilityAmount = BigInt(swapQuote.amountIn);
1946
+ const liabilityAmount = getSwapInputAmount(swapQuote, swapperMode ?? SwapperMode.EXACT_IN);
1642
1947
  // 2. Determine if collateral needs to be enabled
1643
1948
  const hasCollateralInput = collateralShareSource
1644
1949
  ? collateralShareSource.shares > 0n
1645
1950
  : collateralAmount > 0n;
1646
1951
  const enableCollateral = hasCollateralInput &&
1647
1952
  !(account?.isCollateralEnabled(receiver, collateralVault) ?? false);
1953
+ const enableCollateralLong = !(account?.isCollateralEnabled(receiver, longVault) ?? false);
1648
1954
  const resolvedCollateralShareSource = collateralShareSource
1649
1955
  ? {
1650
1956
  ...collateralShareSource,
@@ -1671,7 +1977,9 @@ export class ExecutionService {
1671
1977
  currentController: currentController || undefined,
1672
1978
  enableController,
1673
1979
  collateralShareSource: resolvedCollateralShareSource,
1980
+ collateralWrappedNativeInfo,
1674
1981
  swapQuote,
1982
+ enableCollateralLong,
1675
1983
  // Permit2 is handled separately in the plan
1676
1984
  });
1677
1985
  plan.push(...this.convertBatchItemsToPlan(batchItems, "multiplyWithSwap"));
@@ -1693,7 +2001,7 @@ export class ExecutionService {
1693
2001
  * @returns Array of transaction plan items (optional approval + EVC batch)
1694
2002
  */
1695
2003
  planMultiplySameAsset(args) {
1696
- const { collateralVault, collateralAmount, collateralAsset, collateralShareSource, liabilityVault, liabilityAmount, longVault, receiver, account, } = args;
2004
+ const { collateralVault, collateralAmount, collateralAsset, collateralShareSource, collateralWrappedNativeInfo, liabilityVault, liabilityAmount, longVault, receiver, account, } = args;
1697
2005
  const plan = [];
1698
2006
  // 1. Check if collateral approval is needed (only if depositing collateral)
1699
2007
  if (!collateralShareSource && collateralAmount > 0n) {
@@ -1712,6 +2020,7 @@ export class ExecutionService {
1712
2020
  : collateralAmount > 0n;
1713
2021
  const enableCollateral = hasCollateralInput &&
1714
2022
  !(account?.isCollateralEnabled(receiver, collateralVault) ?? false);
2023
+ const enableCollateralLong = !(account?.isCollateralEnabled(receiver, longVault) ?? false);
1715
2024
  const resolvedCollateralShareSource = collateralShareSource
1716
2025
  ? {
1717
2026
  ...collateralShareSource,
@@ -1738,6 +2047,8 @@ export class ExecutionService {
1738
2047
  currentController: currentController || undefined,
1739
2048
  enableController,
1740
2049
  collateralShareSource: resolvedCollateralShareSource,
2050
+ collateralWrappedNativeInfo,
2051
+ enableCollateralLong,
1741
2052
  // Permit2 is handled separately in the plan
1742
2053
  });
1743
2054
  plan.push(...this.convertBatchItemsToPlan(batchItems, "multiplySameAsset"));