@eulerxyz/euler-v2-sdk 0.2.6-beta → 0.2.8-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 (129) hide show
  1. package/dist/src/entities/Account.d.ts +10 -2
  2. package/dist/src/entities/Account.d.ts.map +1 -1
  3. package/dist/src/entities/Account.js +15 -2
  4. package/dist/src/entities/Account.js.map +1 -1
  5. package/dist/src/entities/EVault.d.ts +6 -1
  6. package/dist/src/entities/EVault.d.ts.map +1 -1
  7. package/dist/src/entities/EVault.js.map +1 -1
  8. package/dist/src/entities/Portfolio.d.ts +143 -18
  9. package/dist/src/entities/Portfolio.d.ts.map +1 -1
  10. package/dist/src/entities/Portfolio.js +240 -36
  11. package/dist/src/entities/Portfolio.js.map +1 -1
  12. package/dist/src/plugins/keyring/keyringPlugin.d.ts.map +1 -1
  13. package/dist/src/plugins/keyring/keyringPlugin.js +67 -17
  14. package/dist/src/plugins/keyring/keyringPlugin.js.map +1 -1
  15. package/dist/src/plugins/pyth/pythPlugin.d.ts +3 -1
  16. package/dist/src/plugins/pyth/pythPlugin.d.ts.map +1 -1
  17. package/dist/src/plugins/pyth/pythPlugin.js +101 -6
  18. package/dist/src/plugins/pyth/pythPlugin.js.map +1 -1
  19. package/dist/src/plugins/types.d.ts +38 -2
  20. package/dist/src/plugins/types.d.ts.map +1 -1
  21. package/dist/src/plugins/types.js.map +1 -1
  22. package/dist/src/sdk/buildSDK.d.ts.map +1 -1
  23. package/dist/src/sdk/buildSDK.js +3 -2
  24. package/dist/src/sdk/buildSDK.js.map +1 -1
  25. package/dist/src/sdk/sdk.d.ts +15 -4
  26. package/dist/src/sdk/sdk.d.ts.map +1 -1
  27. package/dist/src/sdk/sdk.js +40 -3
  28. package/dist/src/sdk/sdk.js.map +1 -1
  29. package/dist/src/services/accountService/accountService.d.ts +7 -1
  30. package/dist/src/services/accountService/accountService.d.ts.map +1 -1
  31. package/dist/src/services/accountService/accountService.js +55 -1
  32. package/dist/src/services/accountService/accountService.js.map +1 -1
  33. package/dist/src/services/accountService/adapters/accountOnchainAdapter/accountOnchainAdapter.d.ts.map +1 -1
  34. package/dist/src/services/accountService/adapters/accountOnchainAdapter/accountOnchainAdapter.js +2 -2
  35. package/dist/src/services/accountService/adapters/accountOnchainAdapter/accountOnchainAdapter.js.map +1 -1
  36. package/dist/src/services/accountService/index.d.ts +1 -1
  37. package/dist/src/services/accountService/index.d.ts.map +1 -1
  38. package/dist/src/services/executionService/cowExecutor.d.ts +3 -0
  39. package/dist/src/services/executionService/cowExecutor.d.ts.map +1 -1
  40. package/dist/src/services/executionService/cowExecutor.js.map +1 -1
  41. package/dist/src/services/executionService/execute.d.ts +8 -0
  42. package/dist/src/services/executionService/execute.d.ts.map +1 -1
  43. package/dist/src/services/executionService/execute.js.map +1 -1
  44. package/dist/src/services/executionService/executionService.d.ts +70 -8
  45. package/dist/src/services/executionService/executionService.d.ts.map +1 -1
  46. package/dist/src/services/executionService/executionService.js +278 -124
  47. package/dist/src/services/executionService/executionService.js.map +1 -1
  48. package/dist/src/services/executionService/executionServiceTypes.d.ts +4 -0
  49. package/dist/src/services/executionService/executionServiceTypes.d.ts.map +1 -1
  50. package/dist/src/services/executionService/simulate.d.ts +29 -0
  51. package/dist/src/services/executionService/simulate.d.ts.map +1 -1
  52. package/dist/src/services/executionService/simulate.js +42 -5
  53. package/dist/src/services/executionService/simulate.js.map +1 -1
  54. package/dist/src/services/rewardsService/adapters/rewardsDirectAdapter/rewardsDirectAdapter.d.ts +2 -1
  55. package/dist/src/services/rewardsService/adapters/rewardsDirectAdapter/rewardsDirectAdapter.d.ts.map +1 -1
  56. package/dist/src/services/rewardsService/adapters/rewardsDirectAdapter/rewardsDirectAdapter.js +2 -2
  57. package/dist/src/services/rewardsService/adapters/rewardsDirectAdapter/rewardsDirectAdapter.js.map +1 -1
  58. package/dist/src/services/rewardsService/adapters/rewardsV3Adapter/rewardsV3Adapter.d.ts +2 -1
  59. package/dist/src/services/rewardsService/adapters/rewardsV3Adapter/rewardsV3Adapter.d.ts.map +1 -1
  60. package/dist/src/services/rewardsService/adapters/rewardsV3Adapter/rewardsV3Adapter.js +11 -2
  61. package/dist/src/services/rewardsService/adapters/rewardsV3Adapter/rewardsV3Adapter.js.map +1 -1
  62. package/dist/src/services/rewardsService/adapters/rewardsV3Adapter/rewardsV3AdapterTypes.d.ts +4 -0
  63. package/dist/src/services/rewardsService/adapters/rewardsV3Adapter/rewardsV3AdapterTypes.d.ts.map +1 -1
  64. package/dist/src/services/rewardsService/index.d.ts +4 -1
  65. package/dist/src/services/rewardsService/index.d.ts.map +1 -1
  66. package/dist/src/services/rewardsService/index.js +2 -0
  67. package/dist/src/services/rewardsService/index.js.map +1 -1
  68. package/dist/src/services/rewardsService/rewardCampaignEligibility.d.ts +32 -0
  69. package/dist/src/services/rewardsService/rewardCampaignEligibility.d.ts.map +1 -0
  70. package/dist/src/services/rewardsService/rewardCampaignEligibility.js +21 -0
  71. package/dist/src/services/rewardsService/rewardCampaignEligibility.js.map +1 -0
  72. package/dist/src/services/rewardsService/rewardsService.d.ts +6 -0
  73. package/dist/src/services/rewardsService/rewardsService.d.ts.map +1 -1
  74. package/dist/src/services/rewardsService/rewardsService.js +20 -3
  75. package/dist/src/services/rewardsService/rewardsService.js.map +1 -1
  76. package/dist/src/services/rewardsService/rewardsServiceTypes.d.ts +8 -5
  77. package/dist/src/services/rewardsService/rewardsServiceTypes.d.ts.map +1 -1
  78. package/dist/src/services/rewardsService/rewardsServiceTypes.js +1 -1
  79. package/dist/src/services/rewardsService/rewardsServiceTypes.js.map +1 -1
  80. package/dist/src/services/rewardsService/vaultRewardInfo.d.ts +25 -0
  81. package/dist/src/services/rewardsService/vaultRewardInfo.d.ts.map +1 -0
  82. package/dist/src/services/rewardsService/vaultRewardInfo.js +39 -0
  83. package/dist/src/services/rewardsService/vaultRewardInfo.js.map +1 -0
  84. package/dist/src/services/vaults/eVaultService/adapters/eVaultOnchainAdapter/eVaultLensTypes.d.ts +2 -1
  85. package/dist/src/services/vaults/eVaultService/adapters/eVaultOnchainAdapter/eVaultLensTypes.d.ts.map +1 -1
  86. package/dist/src/services/vaults/eVaultService/adapters/eVaultOnchainAdapter/eVaultLensTypes.js +1 -0
  87. package/dist/src/services/vaults/eVaultService/adapters/eVaultOnchainAdapter/eVaultLensTypes.js.map +1 -1
  88. package/dist/src/services/vaults/eVaultService/adapters/eVaultOnchainAdapter/vaultInfoConverter.d.ts.map +1 -1
  89. package/dist/src/services/vaults/eVaultService/adapters/eVaultOnchainAdapter/vaultInfoConverter.js +7 -0
  90. package/dist/src/services/vaults/eVaultService/adapters/eVaultOnchainAdapter/vaultInfoConverter.js.map +1 -1
  91. package/dist/src/services/vaults/eVaultService/adapters/eVaultV3Adapter/eVaultV3AdapterConversions.d.ts.map +1 -1
  92. package/dist/src/services/vaults/eVaultService/adapters/eVaultV3Adapter/eVaultV3AdapterConversions.js +17 -6
  93. package/dist/src/services/vaults/eVaultService/adapters/eVaultV3Adapter/eVaultV3AdapterConversions.js.map +1 -1
  94. package/dist/src/services/vaults/eulerEarnService/adapters/eulerEarnOnchainAdapter.d.ts.map +1 -1
  95. package/dist/src/services/vaults/eulerEarnService/adapters/eulerEarnOnchainAdapter.js +52 -17
  96. package/dist/src/services/vaults/eulerEarnService/adapters/eulerEarnOnchainAdapter.js.map +1 -1
  97. package/dist/src/utils/accountComputations.d.ts +41 -8
  98. package/dist/src/utils/accountComputations.d.ts.map +1 -1
  99. package/dist/src/utils/accountComputations.js +127 -77
  100. package/dist/src/utils/accountComputations.js.map +1 -1
  101. package/dist/src/utils/irm.d.ts +10 -2
  102. package/dist/src/utils/irm.d.ts.map +1 -1
  103. package/dist/src/utils/irm.js +33 -0
  104. package/dist/src/utils/irm.js.map +1 -1
  105. package/dist/src/utils/oracle.d.ts +16 -0
  106. package/dist/src/utils/oracle.d.ts.map +1 -1
  107. package/dist/src/utils/oracle.js +98 -0
  108. package/dist/src/utils/oracle.js.map +1 -1
  109. package/dist/src/utils/stateOverrides/approvalOverrides.d.ts +25 -8
  110. package/dist/src/utils/stateOverrides/approvalOverrides.d.ts.map +1 -1
  111. package/dist/src/utils/stateOverrides/approvalOverrides.js +76 -16
  112. package/dist/src/utils/stateOverrides/approvalOverrides.js.map +1 -1
  113. package/dist/src/utils/stateOverrides/balanceOverrides.d.ts +26 -7
  114. package/dist/src/utils/stateOverrides/balanceOverrides.d.ts.map +1 -1
  115. package/dist/src/utils/stateOverrides/balanceOverrides.js +72 -13
  116. package/dist/src/utils/stateOverrides/balanceOverrides.js.map +1 -1
  117. package/dist/src/utils/stateOverrides/getStateOverrides.d.ts +38 -0
  118. package/dist/src/utils/stateOverrides/getStateOverrides.d.ts.map +1 -1
  119. package/dist/src/utils/stateOverrides/getStateOverrides.js +14 -4
  120. package/dist/src/utils/stateOverrides/getStateOverrides.js.map +1 -1
  121. package/dist/src/utils/stateOverrides/index.d.ts +3 -2
  122. package/dist/src/utils/stateOverrides/index.d.ts.map +1 -1
  123. package/dist/src/utils/stateOverrides/index.js +2 -1
  124. package/dist/src/utils/stateOverrides/index.js.map +1 -1
  125. package/dist/src/utils/stateOverrides/slotHints.d.ts +78 -0
  126. package/dist/src/utils/stateOverrides/slotHints.d.ts.map +1 -0
  127. package/dist/src/utils/stateOverrides/slotHints.js +152 -0
  128. package/dist/src/utils/stateOverrides/slotHints.js.map +1 -0
  129. package/package.json +1 -1
@@ -2,6 +2,7 @@ import { decodeFunctionData, encodeFunctionData, erc20Abi, getAddress, maxUint16
2
2
  import { resolveBorrowCollateralPositions } from "../../utils/accountPositionClassification.js";
3
3
  import { SwapperMode } from "../swapService/swapServiceTypes.js";
4
4
  import { adjustForInterest, getSwapInputAmount, } from "../swapService/swapVerification.js";
5
+ import { isEVault, } from "../vaults/vaultMetaService/index.js";
5
6
  import { ethereumVaultConnectorAbi } from "./abis/ethereumVaultConnectorAbi.js";
6
7
  import { eVaultAbi } from "./abis/eVaultAbi.js";
7
8
  import { permit2PermitAbi } from "./abis/permit2PermitAbi.js";
@@ -252,6 +253,7 @@ export class ExecutionService {
252
253
  intrinsicApyService;
253
254
  eulerLabelsService;
254
255
  processPlugins;
256
+ prefetchPlugins;
255
257
  constructor(deploymentService, walletService, providerService, vaultMetaService, priceService, rewardsService, intrinsicApyService, eulerLabelsService) {
256
258
  this.deploymentService = deploymentService;
257
259
  this.walletService = walletService;
@@ -289,6 +291,9 @@ export class ExecutionService {
289
291
  setPluginProcessor(processPlugins) {
290
292
  this.processPlugins = processPlugins;
291
293
  }
294
+ setPluginPrefetcher(prefetchPlugins) {
295
+ this.prefetchPlugins = prefetchPlugins;
296
+ }
292
297
  /** Derive storage overrides needed to simulate the plan against the current account state. */
293
298
  async deriveStateOverrides(chainId, account, transactionPlan, options) {
294
299
  assertNoCowSwapPlanItems(transactionPlan, "deriveStateOverrides");
@@ -297,7 +302,7 @@ export class ExecutionService {
297
302
  /** Simulate the full transaction plan, including approval resolution and plugin-aware batch execution. */
298
303
  async simulateTransactionPlan(chainId, account, transactionPlan, options) {
299
304
  assertNoCowSwapPlanItems(transactionPlan, "simulateTransactionPlan");
300
- const processedPlan = await this.processPlanPlugins(transactionPlan, account, chainId);
305
+ const processedPlan = await this.processPlanPlugins(transactionPlan, account, chainId, options?.prefetch);
301
306
  const owner = this.getAccountOwner(account);
302
307
  return simulateTransactionPlan(this.getSimulationContext(), chainId, owner, processedPlan, options);
303
308
  }
@@ -319,9 +324,9 @@ export class ExecutionService {
319
324
  * wallet allowances.
320
325
  */
321
326
  async prepareTransactionPlan(args) {
322
- const { plan, chainId, account, usePermit2 = true, unlimitedApproval = false, } = args;
327
+ const { plan, chainId, account, usePermit2 = true, unlimitedApproval = false, prefetch, } = args;
323
328
  assertNoCowSwapPlanItems(plan, "prepareTransactionPlan");
324
- const processedPlan = await this.processPlanPlugins(plan, account, chainId);
329
+ const processedPlan = await this.processPlanPlugins(plan, account, chainId, prefetch);
325
330
  const owner = this.getAccountOwner(account);
326
331
  const resolvedPlan = await this.resolveRequiredApprovals({
327
332
  plan: processedPlan,
@@ -342,10 +347,24 @@ export class ExecutionService {
342
347
  /** Estimate gas for the full transaction plan after applying the same simulation pipeline used for execution. */
343
348
  async estimateGasForTransactionPlan(chainId, account, transactionPlan, options) {
344
349
  assertNoCowSwapPlanItems(transactionPlan, "estimateGasForTransactionPlan");
345
- const processedPlan = await this.processPlanPlugins(transactionPlan, account, chainId);
350
+ const processedPlan = await this.processPlanPlugins(transactionPlan, account, chainId, options?.prefetch);
346
351
  const owner = this.getAccountOwner(account);
347
352
  return estimateGasForTransactionPlan(this.getSimulationContext(), chainId, owner, processedPlan, options);
348
353
  }
354
+ /**
355
+ * Estimate gas for a {@link TransactionPlanPrepared} envelope. Plugins and
356
+ * approval resolution were already applied by {@link prepareTransactionPlan},
357
+ * so this skips the plugin pipeline (TOS / Keyring / Pyth) entirely — the
358
+ * plan's batch items are used as-is. Net effect: per-call gas estimate
359
+ * drops from "plugin pipeline + estimate" to just the estimate, mirroring
360
+ * the simulatePreparedTransactionPlan / executePreparedTransactionPlan
361
+ * pattern.
362
+ */
363
+ async estimateGasForPreparedTransactionPlan(prepared, options) {
364
+ assertNoCowSwapPlanItems(prepared.plan, "estimateGasForPreparedTransactionPlan");
365
+ const owner = this.getAccountOwner(prepared.account);
366
+ return estimateGasForTransactionPlan(this.getSimulationContext(), prepared.chainId, owner, prepared.plan, options);
367
+ }
349
368
  /** Execute a transaction plan using caller-provided signing and send callbacks. */
350
369
  async executeTransactionPlan(args) {
351
370
  const { providerService } = this;
@@ -353,7 +372,7 @@ export class ExecutionService {
353
372
  throw new Error("ExecutionService.executeTransactionPlan requires a providerService. Pass it to the ExecutionService constructor or call setProviderService().");
354
373
  }
355
374
  assertNoCowSwapPlanItems(args.plan, "executeTransactionPlan");
356
- const processedPlan = await this.processPlanPlugins(args.plan, args.account, args.chainId);
375
+ const processedPlan = await this.processPlanPlugins(args.plan, args.account, args.chainId, args.prefetch);
357
376
  assertNoCowSwapPlanItems(processedPlan, "executeTransactionPlan");
358
377
  const helperArgs = {
359
378
  ...args,
@@ -399,7 +418,7 @@ export class ExecutionService {
399
418
  }
400
419
  return executeCowSwapTransactionPlan({
401
420
  ...args,
402
- plan: await this.processPlanPlugins(args.plan, args.account, args.chainId),
421
+ plan: await this.processPlanPlugins(args.plan, args.account, args.chainId, args.prefetch),
403
422
  deploymentService: this.deploymentService,
404
423
  providerService,
405
424
  });
@@ -417,11 +436,31 @@ export class ExecutionService {
417
436
  describeBatch: (batch) => this.describeBatch(batch),
418
437
  };
419
438
  }
420
- async processPlanPlugins(plan, account, chainId) {
439
+ /**
440
+ * Run each plugin's processPlan in registration order. Plugins receive the
441
+ * plan as modified by previous plugins; errors are caught per-plugin so a
442
+ * single failing plugin can't poison the pipeline.
443
+ *
444
+ * `prefetch` carries form-level data each plugin pre-resolved via
445
+ * {@link prefetchPluginDataForPlan} — passing it lets the plugin skip its
446
+ * own network I/O (Hermes pulls, keyring hook reads, etc.).
447
+ */
448
+ async processPlanPlugins(plan, account, chainId, prefetch) {
421
449
  return this.processPlugins
422
- ? this.processPlugins(plan, account, chainId)
450
+ ? this.processPlugins(plan, account, chainId, prefetch)
423
451
  : plan;
424
452
  }
453
+ /**
454
+ * Resolve each plugin's prefetch payload for a given plan once, so per-quote
455
+ * prepare/estimate/simulate calls can reuse the data instead of refetching.
456
+ * Returns an open record keyed by plugin name — known SDK slots are typed
457
+ * (`pyth`, `keyring`); external plugins populate their own keys.
458
+ */
459
+ async prefetchPluginDataForPlan(plan, account, chainId) {
460
+ return this.prefetchPlugins
461
+ ? this.prefetchPlugins(plan, account, chainId)
462
+ : {};
463
+ }
425
464
  getAccountOwner(account) {
426
465
  return typeof account === "string" ? account : account.owner;
427
466
  }
@@ -1033,10 +1072,19 @@ export class ExecutionService {
1033
1072
  const transferredPositions = new Set();
1034
1073
  for (const collateral of activeCollaterals) {
1035
1074
  batchItems.push(this.encodeDisableCollateral(account.chainId, receiver, collateral.vaultAddress));
1036
- if (getAddress(receiver) !== getAddress(account.owner)) {
1075
+ // Only EVK vaults expose the transferFromMax used by the cleanup
1076
+ // sweep. Other vault types (e.g. Securitize RWA collaterals) don't
1077
+ // implement it and the underlying transfer would revert against the
1078
+ // ERC-20 whitelist anyway, taking the whole repay batch down. The
1079
+ // disable step above still runs for every active collateral.
1080
+ const isEvkCollateral = isEVault(collateral.vault);
1081
+ if (isEvkCollateral &&
1082
+ getAddress(receiver) !== getAddress(account.owner)) {
1037
1083
  batchItems.push(this.encodeTransferFromMax(collateral.vaultAddress, receiver, account.owner));
1038
1084
  }
1039
- transferredPositions.add(`${getAddress(receiver)}:${getAddress(collateral.vaultAddress)}`);
1085
+ if (isEvkCollateral) {
1086
+ transferredPositions.add(`${getAddress(receiver)}:${getAddress(collateral.vaultAddress)}`);
1087
+ }
1040
1088
  }
1041
1089
  if (sourceAccount && sourceVault) {
1042
1090
  const sourcePositionKey = `${getAddress(sourceAccount)}:${getAddress(sourceVault)}`;
@@ -1113,6 +1161,14 @@ export class ExecutionService {
1113
1161
  continue;
1114
1162
  }
1115
1163
  if (usePermit2) {
1164
+ // Skip Permit2 entirely when an existing direct token→vault
1165
+ // allowance already covers the requirement. Avoids prompting
1166
+ // the user for a Permit2 approve+signature when a prior
1167
+ // direct ERC-20 approval is sufficient.
1168
+ if (allowances.assetForVault >= amount) {
1169
+ approval.resolved = [];
1170
+ continue;
1171
+ }
1116
1172
  // Check permit2 allowances
1117
1173
  const assetForPermit2 = allowances.assetForPermit2;
1118
1174
  const assetForVaultInPermit2 = allowances.assetForVaultInPermit2;
@@ -1204,45 +1260,58 @@ export class ExecutionService {
1204
1260
  unlimitedApproval,
1205
1261
  });
1206
1262
  }
1207
- // ========== Transaction plan functions ==========
1208
- /**
1209
- * Builds a cleanup plan matching Lite's stale collateral/controller policy.
1210
- *
1211
- * Cleanup is derived from the provided account snapshot:
1212
- * - no enabled controllers: disable all enabled collaterals
1213
- * - enabled controllers but no active borrows: disable all collaterals, then controllers
1214
- * - active borrows: disable only enabled collaterals with no supplied position
1215
- *
1216
- * If the sub-account snapshot is unavailable, no cleanup is planned.
1217
- */
1218
- planCleanup(args) {
1219
- const { account, subAccount } = args;
1263
+ buildCleanupBatchItems(account, subAccount, options = {}) {
1264
+ const { skipCollateralCleanup = false, controllerToKeep } = options;
1265
+ const normalizedControllerToKeep = controllerToKeep
1266
+ ? getAddress(controllerToKeep)
1267
+ : undefined;
1268
+ const empty = () => ({
1269
+ items: [],
1270
+ disabledControllers: new Set(),
1271
+ disabledCollaterals: new Set(),
1272
+ });
1220
1273
  if (typeof account.getSubAccount !== "function")
1221
- return [];
1274
+ return empty();
1222
1275
  const subAccountSnapshot = account.getSubAccount(subAccount);
1223
1276
  if (!subAccountSnapshot)
1224
- return [];
1277
+ return empty();
1225
1278
  const collaterals = subAccountSnapshot.enabledCollaterals ?? [];
1226
1279
  const controllers = subAccountSnapshot.enabledControllers ?? [];
1227
1280
  if (collaterals.length === 0 && controllers.length === 0)
1228
- return [];
1281
+ return empty();
1229
1282
  const items = [];
1283
+ const disabledControllers = new Set();
1284
+ const disabledCollaterals = new Set();
1285
+ const disableCollateral = (collateral) => {
1286
+ if (skipCollateralCleanup)
1287
+ return;
1288
+ const normalizedCollateral = getAddress(collateral);
1289
+ disabledCollaterals.add(normalizedCollateral);
1290
+ items.push(this.encodeDisableCollateral(account.chainId, subAccount, normalizedCollateral));
1291
+ };
1230
1292
  const disableAllCollaterals = () => {
1231
1293
  for (const collateral of collaterals) {
1232
- items.push(this.encodeDisableCollateral(account.chainId, subAccount, collateral));
1294
+ disableCollateral(collateral);
1233
1295
  }
1234
1296
  };
1297
+ const disableController = (controller) => {
1298
+ const normalizedController = getAddress(controller);
1299
+ if (normalizedController === normalizedControllerToKeep)
1300
+ return;
1301
+ disabledControllers.add(normalizedController);
1302
+ items.push(this.encodeDisableController(normalizedController, subAccount));
1303
+ };
1235
1304
  if (controllers.length === 0) {
1236
1305
  disableAllCollaterals();
1237
- return this.convertBatchItemsToPlan(items, "cleanup");
1306
+ return { items, disabledControllers, disabledCollaterals };
1238
1307
  }
1239
1308
  const hasActiveBorrow = subAccountSnapshot.positions.some((position) => position.borrowed > 0n);
1240
1309
  if (!hasActiveBorrow) {
1241
1310
  disableAllCollaterals();
1242
1311
  for (const controller of controllers) {
1243
- items.push(this.encodeDisableController(controller, subAccount));
1312
+ disableController(controller);
1244
1313
  }
1245
- return this.convertBatchItemsToPlan(items, "cleanup");
1314
+ return { items, disabledControllers, disabledCollaterals };
1246
1315
  }
1247
1316
  const depositedVaults = new Set(subAccountSnapshot.positions
1248
1317
  .filter(hasSuppliedPosition)
@@ -1250,9 +1319,25 @@ export class ExecutionService {
1250
1319
  for (const collateral of collaterals) {
1251
1320
  if (depositedVaults.has(getAddress(collateral)))
1252
1321
  continue;
1253
- items.push(this.encodeDisableCollateral(account.chainId, subAccount, collateral));
1322
+ disableCollateral(collateral);
1254
1323
  }
1255
- return this.convertBatchItemsToPlan(items, "cleanup");
1324
+ return { items, disabledControllers, disabledCollaterals };
1325
+ }
1326
+ // ========== Transaction plan functions ==========
1327
+ /**
1328
+ * Builds a cleanup plan matching Lite's stale collateral/controller policy.
1329
+ *
1330
+ * Cleanup is derived from the provided account snapshot:
1331
+ * - no enabled controllers: disable all enabled collaterals
1332
+ * - enabled controllers but no active borrows: disable all collaterals, then controllers
1333
+ * - active borrows: disable only enabled collaterals with no supplied position
1334
+ *
1335
+ * If the sub-account snapshot is unavailable, no cleanup is planned.
1336
+ */
1337
+ planCleanup(args) {
1338
+ const { account, subAccount } = args;
1339
+ const cleanup = this.buildCleanupBatchItems(account, subAccount);
1340
+ return this.convertBatchItemsToPlan(cleanup.items, "cleanup");
1256
1341
  }
1257
1342
  /**
1258
1343
  * Builds a transaction plan for depositing assets into a vault.
@@ -1430,8 +1515,18 @@ export class ExecutionService {
1430
1515
  * @returns Array of transaction plan items (optional approval + EVC batch)
1431
1516
  */
1432
1517
  planBorrow(args) {
1433
- const { vault, amount, receiver, borrowAccount, account, collateral } = args;
1518
+ const { vault, amount, receiver, borrowAccount, account, collateral, skipCleanup = false, } = args;
1434
1519
  const plan = [];
1520
+ const cleanup = skipCleanup
1521
+ ? {
1522
+ items: [],
1523
+ disabledControllers: new Set(),
1524
+ disabledCollaterals: new Set(),
1525
+ }
1526
+ : this.buildCleanupBatchItems(account, borrowAccount, {
1527
+ skipCollateralCleanup: true,
1528
+ controllerToKeep: vault,
1529
+ });
1435
1530
  const savingsCollateral = isSavingsCollateral(collateral)
1436
1531
  ? collateral
1437
1532
  : undefined;
@@ -1439,13 +1534,19 @@ export class ExecutionService {
1439
1534
  ? collateral
1440
1535
  : undefined;
1441
1536
  const enableCollateral = collateral && collateral.amount > 0n
1442
- ? !(account?.isCollateralEnabled(borrowAccount, collateral.vault) ??
1443
- false)
1537
+ ? cleanup.disabledCollaterals.has(getAddress(collateral.vault)) ||
1538
+ !(account?.isCollateralEnabled(borrowAccount, collateral.vault) ??
1539
+ false)
1444
1540
  : false;
1445
1541
  // Check if controller needs to be enabled
1446
1542
  // Default: controller is not enabled when account/sub-account is not available
1447
- const currentController = account?.getCurrentController(borrowAccount);
1448
- const enableController = !(account?.isControllerEnabled(borrowAccount, vault) ?? false);
1543
+ const currentControllerBeforeCleanup = account?.getCurrentController(borrowAccount);
1544
+ const currentController = currentControllerBeforeCleanup &&
1545
+ !cleanup.disabledControllers.has(getAddress(currentControllerBeforeCleanup))
1546
+ ? currentControllerBeforeCleanup
1547
+ : undefined;
1548
+ const enableController = cleanup.disabledControllers.size > 0 ||
1549
+ !(account?.isControllerEnabled(borrowAccount, vault) ?? false);
1449
1550
  if (walletCollateral && walletCollateral.amount > 0n) {
1450
1551
  // Approval is needed from the account owner (who owns the wallet tokens)
1451
1552
  // Add approval requirement (will be resolved later with Wallet data)
@@ -1457,29 +1558,32 @@ export class ExecutionService {
1457
1558
  amount: walletCollateral.amount,
1458
1559
  });
1459
1560
  }
1460
- const batchItems = this.encodeBorrow({
1461
- chainId: account.chainId,
1462
- vault,
1463
- amount,
1464
- owner: account.owner,
1465
- borrowAccount,
1466
- receiver,
1467
- enableController,
1468
- currentController: currentController || undefined,
1469
- enableCollateral,
1470
- collateralVault: collateral?.vault,
1471
- collateralAmount: walletCollateral?.amount,
1472
- collateralWrappedNativeInfo: walletCollateral?.wrappedNativeInfo,
1473
- collateralShareSource: savingsCollateral
1474
- ? {
1475
- from: savingsCollateral.from,
1476
- shares: savingsCollateral.amount,
1477
- disableCollateralFrom: savingsCollateral.disableCollateralFrom &&
1478
- (account?.isCollateralEnabled(savingsCollateral.from, savingsCollateral.vault) ??
1479
- false),
1480
- }
1481
- : undefined,
1482
- });
1561
+ const batchItems = [
1562
+ ...cleanup.items,
1563
+ ...this.encodeBorrow({
1564
+ chainId: account.chainId,
1565
+ vault,
1566
+ amount,
1567
+ owner: account.owner,
1568
+ borrowAccount,
1569
+ receiver,
1570
+ enableController,
1571
+ currentController: currentController || undefined,
1572
+ enableCollateral,
1573
+ collateralVault: collateral?.vault,
1574
+ collateralAmount: walletCollateral?.amount,
1575
+ collateralWrappedNativeInfo: walletCollateral?.wrappedNativeInfo,
1576
+ collateralShareSource: savingsCollateral
1577
+ ? {
1578
+ from: savingsCollateral.from,
1579
+ shares: savingsCollateral.amount,
1580
+ disableCollateralFrom: savingsCollateral.disableCollateralFrom &&
1581
+ (account?.isCollateralEnabled(savingsCollateral.from, savingsCollateral.vault) ??
1582
+ false),
1583
+ }
1584
+ : undefined,
1585
+ }),
1586
+ ];
1483
1587
  plan.push(...this.convertBatchItemsToPlan(batchItems, "borrow"));
1484
1588
  return plan;
1485
1589
  }
@@ -1862,9 +1966,16 @@ export class ExecutionService {
1862
1966
  return plan;
1863
1967
  }
1864
1968
  planSwapAndBorrowFromWallet(args) {
1865
- const { swapQuote, amount, tokenIn, account, borrowVault, borrowAmount, borrowAccount = swapQuote.accountOut, receiver = account.owner, collateralVault = swapQuote.receiver, wrappedNativeInfo, } = args;
1969
+ const { swapQuote, amount, tokenIn, account, borrowVault, borrowAmount, borrowAccount = swapQuote.accountOut, receiver = account.owner, collateralVault = swapQuote.receiver, wrappedNativeInfo, skipCleanup = false, } = args;
1866
1970
  assertNonCowSwapQuote(swapQuote, "planSwapAndBorrowFromWallet");
1867
1971
  const plan = [];
1972
+ const cleanup = skipCleanup
1973
+ ? {
1974
+ items: [],
1975
+ disabledControllers: new Set(),
1976
+ disabledCollaterals: new Set(),
1977
+ }
1978
+ : this.buildCleanupBatchItems(account, borrowAccount);
1868
1979
  plan.push({
1869
1980
  type: "requiredApproval",
1870
1981
  token: tokenIn,
@@ -1872,24 +1983,33 @@ export class ExecutionService {
1872
1983
  spender: swapQuote.verify.verifierAddress,
1873
1984
  amount,
1874
1985
  });
1875
- const enableController = !(account?.isControllerEnabled(borrowAccount, borrowVault) ?? false);
1876
- const enableCollateral = !(account?.isCollateralEnabled(borrowAccount, collateralVault) ?? false);
1877
- const currentController = account?.getCurrentController(borrowAccount);
1878
- const batchItems = this.encodeSwapAndBorrowFromWallet({
1879
- chainId: account.chainId,
1880
- swapQuote,
1881
- amount,
1882
- sender: account.owner,
1883
- borrowAccount,
1884
- collateralVault,
1885
- borrowVault,
1886
- borrowAmount,
1887
- receiver,
1888
- currentController: currentController || undefined,
1889
- enableController,
1890
- enableCollateral,
1891
- wrappedNativeInfo,
1892
- });
1986
+ const enableController = cleanup.disabledControllers.size > 0 ||
1987
+ !(account?.isControllerEnabled(borrowAccount, borrowVault) ?? false);
1988
+ const enableCollateral = cleanup.disabledCollaterals.has(getAddress(collateralVault)) ||
1989
+ !(account?.isCollateralEnabled(borrowAccount, collateralVault) ?? false);
1990
+ const currentControllerBeforeCleanup = account?.getCurrentController(borrowAccount);
1991
+ const currentController = currentControllerBeforeCleanup &&
1992
+ !cleanup.disabledControllers.has(getAddress(currentControllerBeforeCleanup))
1993
+ ? currentControllerBeforeCleanup
1994
+ : undefined;
1995
+ const batchItems = [
1996
+ ...cleanup.items,
1997
+ ...this.encodeSwapAndBorrowFromWallet({
1998
+ chainId: account.chainId,
1999
+ swapQuote,
2000
+ amount,
2001
+ sender: account.owner,
2002
+ borrowAccount,
2003
+ collateralVault,
2004
+ borrowVault,
2005
+ borrowAmount,
2006
+ receiver,
2007
+ currentController: currentController || undefined,
2008
+ enableController,
2009
+ enableCollateral,
2010
+ wrappedNativeInfo,
2011
+ }),
2012
+ ];
1893
2013
  plan.push(...this.convertBatchItemsToPlan(batchItems, "swapAndBorrowFromWallet"));
1894
2014
  return plan;
1895
2015
  }
@@ -2229,7 +2349,7 @@ export class ExecutionService {
2229
2349
  * @returns Array of transaction plan items (optional approval + EVC batch). Throws if swapQuote.accountIn !== swapQuote.accountOut.
2230
2350
  */
2231
2351
  planMultiplyWithSwap(args) {
2232
- const { collateralVault, collateralAmount, collateralAsset, collateralShareSource, collateralWrappedNativeInfo, account, swapQuote, swapperMode, } = args;
2352
+ const { collateralVault, collateralAmount, collateralAsset, collateralShareSource, collateralWrappedNativeInfo, account, swapQuote, swapperMode, skipCleanup = false, } = args;
2233
2353
  assertNonCowSwapQuote(swapQuote, "planMultiplyWithSwap", "planOpenPositionWithCoW");
2234
2354
  const plan = [];
2235
2355
  // 1. Check if collateral approval is needed (only if depositing collateral)
@@ -2249,14 +2369,23 @@ export class ExecutionService {
2249
2369
  const receiver = swapQuote.accountIn;
2250
2370
  const liabilityVault = swapQuote.vaultIn;
2251
2371
  const longVault = swapQuote.receiver;
2372
+ const cleanup = skipCleanup
2373
+ ? {
2374
+ items: [],
2375
+ disabledControllers: new Set(),
2376
+ disabledCollaterals: new Set(),
2377
+ }
2378
+ : this.buildCleanupBatchItems(account, receiver);
2252
2379
  const liabilityAmount = getSwapInputAmount(swapQuote, swapperMode ?? SwapperMode.EXACT_IN);
2253
2380
  // 2. Determine if collateral needs to be enabled
2254
2381
  const hasCollateralInput = collateralShareSource
2255
2382
  ? collateralShareSource.shares > 0n
2256
2383
  : collateralAmount > 0n;
2257
2384
  const enableCollateral = hasCollateralInput &&
2258
- !(account?.isCollateralEnabled(receiver, collateralVault) ?? false);
2259
- const enableCollateralLong = !(account?.isCollateralEnabled(receiver, longVault) ?? false);
2385
+ (cleanup.disabledCollaterals.has(getAddress(collateralVault)) ||
2386
+ !(account?.isCollateralEnabled(receiver, collateralVault) ?? false));
2387
+ const enableCollateralLong = cleanup.disabledCollaterals.has(getAddress(longVault)) ||
2388
+ !(account?.isCollateralEnabled(receiver, longVault) ?? false);
2260
2389
  const resolvedCollateralShareSource = collateralShareSource
2261
2390
  ? {
2262
2391
  ...collateralShareSource,
@@ -2266,28 +2395,36 @@ export class ExecutionService {
2266
2395
  }
2267
2396
  : undefined;
2268
2397
  // 3. Determine if controller needs to be enabled
2269
- const enableController = !(account?.isControllerEnabled(receiver, liabilityVault) ?? false);
2398
+ const enableController = cleanup.disabledControllers.size > 0 ||
2399
+ !(account?.isControllerEnabled(receiver, liabilityVault) ?? false);
2270
2400
  // 4. Get current controller (may need to disable if different)
2271
- const currentController = account?.getCurrentController(receiver);
2401
+ const currentControllerBeforeCleanup = account?.getCurrentController(receiver);
2402
+ const currentController = currentControllerBeforeCleanup &&
2403
+ !cleanup.disabledControllers.has(getAddress(currentControllerBeforeCleanup))
2404
+ ? currentControllerBeforeCleanup
2405
+ : undefined;
2272
2406
  // 5. Build EVC batch items
2273
- const batchItems = this.encodeMultiplyWithSwap({
2274
- chainId: account.chainId,
2275
- collateralVault,
2276
- collateralAmount,
2277
- liabilityVault,
2278
- liabilityAmount,
2279
- longVault,
2280
- owner: account.owner,
2281
- receiver,
2282
- enableCollateral,
2283
- currentController: currentController || undefined,
2284
- enableController,
2285
- collateralShareSource: resolvedCollateralShareSource,
2286
- collateralWrappedNativeInfo,
2287
- swapQuote,
2288
- enableCollateralLong,
2289
- // Permit2 is handled separately in the plan
2290
- });
2407
+ const batchItems = [
2408
+ ...cleanup.items,
2409
+ ...this.encodeMultiplyWithSwap({
2410
+ chainId: account.chainId,
2411
+ collateralVault,
2412
+ collateralAmount,
2413
+ liabilityVault,
2414
+ liabilityAmount,
2415
+ longVault,
2416
+ owner: account.owner,
2417
+ receiver,
2418
+ enableCollateral,
2419
+ currentController: currentController || undefined,
2420
+ enableController,
2421
+ collateralShareSource: resolvedCollateralShareSource,
2422
+ collateralWrappedNativeInfo,
2423
+ swapQuote,
2424
+ enableCollateralLong,
2425
+ // Permit2 is handled separately in the plan
2426
+ }),
2427
+ ];
2291
2428
  plan.push(...this.convertBatchItemsToPlan(batchItems, "multiplyWithSwap"));
2292
2429
  return plan;
2293
2430
  }
@@ -2348,8 +2485,15 @@ export class ExecutionService {
2348
2485
  * @returns Array of transaction plan items (optional approval + EVC batch)
2349
2486
  */
2350
2487
  planMultiplySameAsset(args) {
2351
- const { collateralVault, collateralAmount, collateralAsset, collateralShareSource, collateralWrappedNativeInfo, liabilityVault, liabilityAmount, longVault, receiver, account, } = args;
2488
+ const { collateralVault, collateralAmount, collateralAsset, collateralShareSource, collateralWrappedNativeInfo, liabilityVault, liabilityAmount, longVault, receiver, account, skipCleanup = false, } = args;
2352
2489
  const plan = [];
2490
+ const cleanup = skipCleanup
2491
+ ? {
2492
+ items: [],
2493
+ disabledControllers: new Set(),
2494
+ disabledCollaterals: new Set(),
2495
+ }
2496
+ : this.buildCleanupBatchItems(account, receiver);
2353
2497
  // 1. Check if collateral approval is needed (only if depositing collateral)
2354
2498
  if (!collateralShareSource && collateralAmount > 0n) {
2355
2499
  // Add approval requirement (will be resolved later with Wallet data)
@@ -2366,8 +2510,10 @@ export class ExecutionService {
2366
2510
  ? collateralShareSource.shares > 0n
2367
2511
  : collateralAmount > 0n;
2368
2512
  const enableCollateral = hasCollateralInput &&
2369
- !(account?.isCollateralEnabled(receiver, collateralVault) ?? false);
2370
- const enableCollateralLong = !(account?.isCollateralEnabled(receiver, longVault) ?? false);
2513
+ (cleanup.disabledCollaterals.has(getAddress(collateralVault)) ||
2514
+ !(account?.isCollateralEnabled(receiver, collateralVault) ?? false));
2515
+ const enableCollateralLong = cleanup.disabledCollaterals.has(getAddress(longVault)) ||
2516
+ !(account?.isCollateralEnabled(receiver, longVault) ?? false);
2371
2517
  const resolvedCollateralShareSource = collateralShareSource
2372
2518
  ? {
2373
2519
  ...collateralShareSource,
@@ -2377,27 +2523,35 @@ export class ExecutionService {
2377
2523
  }
2378
2524
  : undefined;
2379
2525
  // 3. Determine if controller needs to be enabled
2380
- const enableController = !(account?.isControllerEnabled(receiver, liabilityVault) ?? false);
2526
+ const enableController = cleanup.disabledControllers.size > 0 ||
2527
+ !(account?.isControllerEnabled(receiver, liabilityVault) ?? false);
2381
2528
  // 4. Get current controller (may need to disable if different)
2382
- const currentController = account?.getCurrentController(receiver);
2529
+ const currentControllerBeforeCleanup = account?.getCurrentController(receiver);
2530
+ const currentController = currentControllerBeforeCleanup &&
2531
+ !cleanup.disabledControllers.has(getAddress(currentControllerBeforeCleanup))
2532
+ ? currentControllerBeforeCleanup
2533
+ : undefined;
2383
2534
  // 5. Build EVC batch items
2384
- const batchItems = this.encodeMultiplySameAsset({
2385
- chainId: account.chainId,
2386
- collateralVault,
2387
- collateralAmount,
2388
- liabilityVault,
2389
- liabilityAmount,
2390
- longVault,
2391
- owner: account.owner,
2392
- receiver,
2393
- enableCollateral,
2394
- currentController: currentController || undefined,
2395
- enableController,
2396
- collateralShareSource: resolvedCollateralShareSource,
2397
- collateralWrappedNativeInfo,
2398
- enableCollateralLong,
2399
- // Permit2 is handled separately in the plan
2400
- });
2535
+ const batchItems = [
2536
+ ...cleanup.items,
2537
+ ...this.encodeMultiplySameAsset({
2538
+ chainId: account.chainId,
2539
+ collateralVault,
2540
+ collateralAmount,
2541
+ liabilityVault,
2542
+ liabilityAmount,
2543
+ longVault,
2544
+ owner: account.owner,
2545
+ receiver,
2546
+ enableCollateral,
2547
+ currentController: currentController || undefined,
2548
+ enableController,
2549
+ collateralShareSource: resolvedCollateralShareSource,
2550
+ collateralWrappedNativeInfo,
2551
+ enableCollateralLong,
2552
+ // Permit2 is handled separately in the plan
2553
+ }),
2554
+ ];
2401
2555
  plan.push(...this.convertBatchItemsToPlan(batchItems, "multiplySameAsset"));
2402
2556
  return plan;
2403
2557
  }