@gearbox-protocol/sdk 8.8.1 → 8.10.0

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.
@@ -117,6 +117,12 @@ class AccountOpener extends import_sdk.SDKConstruct {
117
117
  }
118
118
  }
119
119
  this.#logger?.info(`opened ${success}/${targets.length} accounts`);
120
+ try {
121
+ await this.#directlyDistributeTokens(results);
122
+ this.#logger?.info("distributed direct transfer tokens");
123
+ } catch (e) {
124
+ this.#logger?.error(`failed to distribute tokens: ${e}`);
125
+ }
120
126
  return results;
121
127
  }
122
128
  async #openAccount(input, index, total) {
@@ -503,6 +509,87 @@ class AccountOpener extends import_sdk.SDKConstruct {
503
509
  });
504
510
  return acc;
505
511
  }
512
+ /**
513
+ * Claims tokens from faucet, uniformly distributes them to accounts
514
+ * @param targets
515
+ * @returns
516
+ */
517
+ async #directlyDistributeTokens(targets) {
518
+ const tokens = new import_sdk.AddressSet(
519
+ targets.flatMap((t) => t.input.directTransfer ?? [])
520
+ );
521
+ if (tokens.size === 0) {
522
+ return;
523
+ }
524
+ const distributorKey = (0, import_accounts.generatePrivateKey)();
525
+ const distributor = (0, import_accounts.privateKeyToAccount)(distributorKey);
526
+ await this.#anvil.setBalance({
527
+ address: distributor.address,
528
+ value: (0, import_viem.parseEther)("100")
529
+ });
530
+ await (0, import_claimFromFaucet.claimFromFaucet)({
531
+ publicClient: this.#anvil,
532
+ wallet: this.#anvil,
533
+ faucet: this.faucet,
534
+ amountUSD: (minAmountUSD) => minAmountUSD * BigInt(targets.length),
535
+ claimer: distributor,
536
+ role: "reward token distributor",
537
+ logger: this.#logger
538
+ });
539
+ const actualBalances = await this.#anvil.multicall({
540
+ contracts: tokens.map(
541
+ (token) => ({
542
+ address: token,
543
+ abi: import_viem.erc20Abi,
544
+ functionName: "balanceOf",
545
+ args: [distributor.address]
546
+ })
547
+ ),
548
+ allowFailure: false
549
+ });
550
+ const tokensArr = tokens.asArray();
551
+ for (let i = 0; i < tokensArr.length; i++) {
552
+ const token = tokensArr[i];
553
+ const balance = actualBalances[i];
554
+ this.#logger?.debug(`${token} balance: ${balance}`);
555
+ }
556
+ for (const { account, input } of targets) {
557
+ if (!account) {
558
+ continue;
559
+ }
560
+ const directTransfer = new import_sdk.AddressSet(input.directTransfer);
561
+ for (let i = 0; i < tokensArr.length; i++) {
562
+ const token = tokensArr[i];
563
+ if (!directTransfer.has(token)) {
564
+ continue;
565
+ }
566
+ const balance = actualBalances[i] / BigInt(targets.length);
567
+ this.#logger?.debug(
568
+ `sending ${balance} ${token} to ${account.creditAccount}`
569
+ );
570
+ const hash = await this.#anvil.writeContract({
571
+ account: distributor,
572
+ address: token,
573
+ abi: import_viem.erc20Abi,
574
+ functionName: "transfer",
575
+ args: [account.creditAccount, balance],
576
+ chain: this.#anvil.chain
577
+ });
578
+ const receipt = await this.#anvil.waitForTransactionReceipt({
579
+ hash
580
+ });
581
+ if (receipt.status === "reverted") {
582
+ this.#logger?.error(
583
+ `failed to send ${token} to ${account.creditAccount}, tx ${hash} reverted`
584
+ );
585
+ } else {
586
+ this.#logger?.debug(
587
+ `sent ${token} to ${account.creditAccount}, tx: ${hash}`
588
+ );
589
+ }
590
+ }
591
+ }
592
+ }
506
593
  #getCollateralQuota(cm, collateral, collateralAmount, debt, logger) {
507
594
  const {
508
595
  underlying,
@@ -210,19 +210,18 @@ class AbstractCreditAccountService extends import_base.SDKConstruct {
210
210
  }
211
211
  /**
212
212
  * Generates transaction to liquidate credit account
213
- * @param account
214
- * @param to Address to transfer underlying left after liquidation
215
- * @param slippage
216
- * @param force TODO: legacy v3 option to remove
213
+ * @param props - {@link FullyLiquidateProps}
217
214
  * @returns
218
215
  */
219
- async fullyLiquidate(account, to, slippage = 50n, force = false) {
216
+ async fullyLiquidate(props) {
217
+ const { account, to, slippage = 50n, force = false, keepAssets } = props;
220
218
  const cm = this.sdk.marketRegister.findCreditManager(account.creditManager);
221
219
  const routerCloseResult = await this.sdk.routerFor(account).findBestClosePath({
222
220
  creditAccount: account,
223
221
  creditManager: cm.creditManager,
224
222
  slippage,
225
- force
223
+ force,
224
+ keepAssets
226
225
  });
227
226
  const priceUpdates = await this.getPriceUpdatesForFacade(
228
227
  account.creditManager,
@@ -28,8 +28,8 @@ class AbstractRouterContract extends import_base.BaseContract {
28
28
  hooks = new import_internal.Hooks();
29
29
  addHook = this.hooks.addHook.bind(this.hooks);
30
30
  removeHook = this.hooks.removeHook.bind(this.hooks);
31
- getExpectedAndLeftover(ca, cm, balances) {
32
- const b = balances || this.getDefaultExpectedAndLeftover(ca);
31
+ getExpectedAndLeftover(ca, cm, balances, keepAssets) {
32
+ const b = balances || this.getDefaultExpectedAndLeftover(ca, keepAssets);
33
33
  const { leftoverBalances, expectedBalances, tokensToClaim } = b;
34
34
  const expected = new import_utils.AddressMap();
35
35
  const leftover = new import_utils.AddressMap();
@@ -47,16 +47,17 @@ class AbstractRouterContract extends import_base.BaseContract {
47
47
  tokensToClaim
48
48
  };
49
49
  }
50
- getDefaultExpectedAndLeftover(ca) {
50
+ getDefaultExpectedAndLeftover(ca, keepAssets) {
51
51
  const expectedBalances = new import_utils.AddressMap();
52
52
  const leftoverBalances = new import_utils.AddressMap();
53
+ const keepAssetsSet = new Set(keepAssets?.map((a) => a.toLowerCase()));
53
54
  for (const { token: t, balance, mask } of ca.tokens) {
54
55
  const token = t;
55
56
  const isEnabled = (mask & ca.enabledTokensMask) !== 0n;
56
57
  expectedBalances.upsert(token, { token, balance });
57
58
  const decimals = this.sdk.tokensMeta.decimals(token);
58
59
  const minBalance = 10n ** BigInt(Math.max(8, decimals) - 8);
59
- if (balance < minBalance || !isEnabled) {
60
+ if (keepAssetsSet.has(token.toLowerCase()) || balance < minBalance || !isEnabled) {
60
61
  leftoverBalances.upsert(token, { token, balance });
61
62
  }
62
63
  }
@@ -260,11 +260,12 @@ class RouterV300Contract extends import_AbstractRouterContract.AbstractRouterCon
260
260
  /**
261
261
  * Implements {@link IRouterContract.getFindClosePathInput}
262
262
  */
263
- getFindClosePathInput(ca, cm, balances) {
263
+ getFindClosePathInput(ca, cm, balances, keepAssets) {
264
264
  const { expectedBalances, leftoverBalances } = this.getExpectedAndLeftover(
265
265
  ca,
266
266
  cm,
267
- balances
267
+ balances,
268
+ keepAssets
268
269
  );
269
270
  const pathOptions = import_PathOptionFactory.PathOptionFactory.generatePathOptions(
270
271
  ca.tokens,
@@ -164,7 +164,13 @@ class RouterV310Contract extends import_AbstractRouterContract.AbstractRouterCon
164
164
  * Implements {@link IRouterContract.findBestClosePath}
165
165
  */
166
166
  async findBestClosePath(props) {
167
- const { creditAccount: ca, creditManager: cm, slippage, balances } = props;
167
+ const {
168
+ creditAccount: ca,
169
+ creditManager: cm,
170
+ slippage,
171
+ balances,
172
+ keepAssets
173
+ } = props;
168
174
  const { expectedBalances, leftoverBalances, tokensToClaim } = this.getExpectedAndLeftover(
169
175
  ca,
170
176
  cm,
@@ -172,7 +178,8 @@ class RouterV310Contract extends import_AbstractRouterContract.AbstractRouterCon
172
178
  expectedBalances: (0, import_helpers.assetsMap)(balances.expectedBalances),
173
179
  leftoverBalances: (0, import_helpers.assetsMap)(balances.leftoverBalances),
174
180
  tokensToClaim: (0, import_helpers.assetsMap)(balances.tokensToClaim || [])
175
- } : void 0
181
+ } : void 0,
182
+ keepAssets
176
183
  );
177
184
  const getNumSplits = this.#numSplitsGetter(cm, expectedBalances.values());
178
185
  const tData = [];
@@ -0,0 +1,48 @@
1
+ "use strict";
2
+ var __defProp = Object.defineProperty;
3
+ var __getOwnPropDesc = Object.getOwnPropertyDescriptor;
4
+ var __getOwnPropNames = Object.getOwnPropertyNames;
5
+ var __hasOwnProp = Object.prototype.hasOwnProperty;
6
+ var __export = (target, all) => {
7
+ for (var name in all)
8
+ __defProp(target, name, { get: all[name], enumerable: true });
9
+ };
10
+ var __copyProps = (to, from, except, desc) => {
11
+ if (from && typeof from === "object" || typeof from === "function") {
12
+ for (let key of __getOwnPropNames(from))
13
+ if (!__hasOwnProp.call(to, key) && key !== except)
14
+ __defProp(to, key, { get: () => from[key], enumerable: !(desc = __getOwnPropDesc(from, key)) || desc.enumerable });
15
+ }
16
+ return to;
17
+ };
18
+ var __toCommonJS = (mod) => __copyProps(__defProp({}, "__esModule", { value: true }), mod);
19
+ var AddressSet_exports = {};
20
+ __export(AddressSet_exports, {
21
+ AddressSet: () => AddressSet
22
+ });
23
+ module.exports = __toCommonJS(AddressSet_exports);
24
+ var import_viem = require("viem");
25
+ class AddressSet extends Set {
26
+ constructor(entries) {
27
+ super(Array.from(entries ?? []).map((a) => (0, import_viem.getAddress)(a)));
28
+ }
29
+ add(value) {
30
+ return super.add((0, import_viem.getAddress)(value));
31
+ }
32
+ delete(value) {
33
+ return super.delete((0, import_viem.getAddress)(value));
34
+ }
35
+ has(value) {
36
+ return super.has((0, import_viem.getAddress)(value));
37
+ }
38
+ asArray() {
39
+ return Array.from(this);
40
+ }
41
+ map(fn) {
42
+ return this.asArray().map(fn);
43
+ }
44
+ }
45
+ // Annotate the CommonJS export names for ESM import in node:
46
+ 0 && (module.exports = {
47
+ AddressSet
48
+ });
@@ -16,6 +16,7 @@ var __toCommonJS = (mod) => __copyProps(__defProp({}, "__esModule", { value: tru
16
16
  var utils_exports = {};
17
17
  module.exports = __toCommonJS(utils_exports);
18
18
  __reExport(utils_exports, require("./AddressMap.js"), module.exports);
19
+ __reExport(utils_exports, require("./AddressSet.js"), module.exports);
19
20
  __reExport(utils_exports, require("./bytes32ToString.js"), module.exports);
20
21
  __reExport(utils_exports, require("./childLogger.js"), module.exports);
21
22
  __reExport(utils_exports, require("./createRawTx.js"), module.exports);
@@ -32,6 +33,7 @@ __reExport(utils_exports, require("./zod.js"), module.exports);
32
33
  // Annotate the CommonJS export names for ESM import in node:
33
34
  0 && (module.exports = {
34
35
  ...require("./AddressMap.js"),
36
+ ...require("./AddressSet.js"),
35
37
  ...require("./bytes32ToString.js"),
36
38
  ...require("./childLogger.js"),
37
39
  ...require("./createRawTx.js"),
@@ -1,10 +1,17 @@
1
- import { BaseError, isAddress, parseEther, parseEventLogs } from "viem";
1
+ import {
2
+ BaseError,
3
+ erc20Abi,
4
+ isAddress,
5
+ parseEther,
6
+ parseEventLogs
7
+ } from "viem";
2
8
  import { generatePrivateKey, privateKeyToAccount } from "viem/accounts";
3
9
  import { ierc20Abi } from "../abi/iERC20.js";
4
10
  import { iCreditFacadeV300Abi, iPoolV300Abi } from "../abi/v300.js";
5
11
  import {
6
12
  ADDRESS_0X0,
7
13
  AddressMap,
14
+ AddressSet,
8
15
  childLogger,
9
16
  formatBN,
10
17
  MAX_UINT256,
@@ -102,6 +109,12 @@ class AccountOpener extends SDKConstruct {
102
109
  }
103
110
  }
104
111
  this.#logger?.info(`opened ${success}/${targets.length} accounts`);
112
+ try {
113
+ await this.#directlyDistributeTokens(results);
114
+ this.#logger?.info("distributed direct transfer tokens");
115
+ } catch (e) {
116
+ this.#logger?.error(`failed to distribute tokens: ${e}`);
117
+ }
105
118
  return results;
106
119
  }
107
120
  async #openAccount(input, index, total) {
@@ -488,6 +501,87 @@ class AccountOpener extends SDKConstruct {
488
501
  });
489
502
  return acc;
490
503
  }
504
+ /**
505
+ * Claims tokens from faucet, uniformly distributes them to accounts
506
+ * @param targets
507
+ * @returns
508
+ */
509
+ async #directlyDistributeTokens(targets) {
510
+ const tokens = new AddressSet(
511
+ targets.flatMap((t) => t.input.directTransfer ?? [])
512
+ );
513
+ if (tokens.size === 0) {
514
+ return;
515
+ }
516
+ const distributorKey = generatePrivateKey();
517
+ const distributor = privateKeyToAccount(distributorKey);
518
+ await this.#anvil.setBalance({
519
+ address: distributor.address,
520
+ value: parseEther("100")
521
+ });
522
+ await claimFromFaucet({
523
+ publicClient: this.#anvil,
524
+ wallet: this.#anvil,
525
+ faucet: this.faucet,
526
+ amountUSD: (minAmountUSD) => minAmountUSD * BigInt(targets.length),
527
+ claimer: distributor,
528
+ role: "reward token distributor",
529
+ logger: this.#logger
530
+ });
531
+ const actualBalances = await this.#anvil.multicall({
532
+ contracts: tokens.map(
533
+ (token) => ({
534
+ address: token,
535
+ abi: erc20Abi,
536
+ functionName: "balanceOf",
537
+ args: [distributor.address]
538
+ })
539
+ ),
540
+ allowFailure: false
541
+ });
542
+ const tokensArr = tokens.asArray();
543
+ for (let i = 0; i < tokensArr.length; i++) {
544
+ const token = tokensArr[i];
545
+ const balance = actualBalances[i];
546
+ this.#logger?.debug(`${token} balance: ${balance}`);
547
+ }
548
+ for (const { account, input } of targets) {
549
+ if (!account) {
550
+ continue;
551
+ }
552
+ const directTransfer = new AddressSet(input.directTransfer);
553
+ for (let i = 0; i < tokensArr.length; i++) {
554
+ const token = tokensArr[i];
555
+ if (!directTransfer.has(token)) {
556
+ continue;
557
+ }
558
+ const balance = actualBalances[i] / BigInt(targets.length);
559
+ this.#logger?.debug(
560
+ `sending ${balance} ${token} to ${account.creditAccount}`
561
+ );
562
+ const hash = await this.#anvil.writeContract({
563
+ account: distributor,
564
+ address: token,
565
+ abi: erc20Abi,
566
+ functionName: "transfer",
567
+ args: [account.creditAccount, balance],
568
+ chain: this.#anvil.chain
569
+ });
570
+ const receipt = await this.#anvil.waitForTransactionReceipt({
571
+ hash
572
+ });
573
+ if (receipt.status === "reverted") {
574
+ this.#logger?.error(
575
+ `failed to send ${token} to ${account.creditAccount}, tx ${hash} reverted`
576
+ );
577
+ } else {
578
+ this.#logger?.debug(
579
+ `sent ${token} to ${account.creditAccount}, tx: ${hash}`
580
+ );
581
+ }
582
+ }
583
+ }
584
+ }
491
585
  #getCollateralQuota(cm, collateral, collateralAmount, debt, logger) {
492
586
  const {
493
587
  underlying,
@@ -201,19 +201,18 @@ class AbstractCreditAccountService extends SDKConstruct {
201
201
  }
202
202
  /**
203
203
  * Generates transaction to liquidate credit account
204
- * @param account
205
- * @param to Address to transfer underlying left after liquidation
206
- * @param slippage
207
- * @param force TODO: legacy v3 option to remove
204
+ * @param props - {@link FullyLiquidateProps}
208
205
  * @returns
209
206
  */
210
- async fullyLiquidate(account, to, slippage = 50n, force = false) {
207
+ async fullyLiquidate(props) {
208
+ const { account, to, slippage = 50n, force = false, keepAssets } = props;
211
209
  const cm = this.sdk.marketRegister.findCreditManager(account.creditManager);
212
210
  const routerCloseResult = await this.sdk.routerFor(account).findBestClosePath({
213
211
  creditAccount: account,
214
212
  creditManager: cm.creditManager,
215
213
  slippage,
216
- force
214
+ force,
215
+ keepAssets
217
216
  });
218
217
  const priceUpdates = await this.getPriceUpdatesForFacade(
219
218
  account.creditManager,
@@ -5,8 +5,8 @@ class AbstractRouterContract extends BaseContract {
5
5
  hooks = new Hooks();
6
6
  addHook = this.hooks.addHook.bind(this.hooks);
7
7
  removeHook = this.hooks.removeHook.bind(this.hooks);
8
- getExpectedAndLeftover(ca, cm, balances) {
9
- const b = balances || this.getDefaultExpectedAndLeftover(ca);
8
+ getExpectedAndLeftover(ca, cm, balances, keepAssets) {
9
+ const b = balances || this.getDefaultExpectedAndLeftover(ca, keepAssets);
10
10
  const { leftoverBalances, expectedBalances, tokensToClaim } = b;
11
11
  const expected = new AddressMap();
12
12
  const leftover = new AddressMap();
@@ -24,16 +24,17 @@ class AbstractRouterContract extends BaseContract {
24
24
  tokensToClaim
25
25
  };
26
26
  }
27
- getDefaultExpectedAndLeftover(ca) {
27
+ getDefaultExpectedAndLeftover(ca, keepAssets) {
28
28
  const expectedBalances = new AddressMap();
29
29
  const leftoverBalances = new AddressMap();
30
+ const keepAssetsSet = new Set(keepAssets?.map((a) => a.toLowerCase()));
30
31
  for (const { token: t, balance, mask } of ca.tokens) {
31
32
  const token = t;
32
33
  const isEnabled = (mask & ca.enabledTokensMask) !== 0n;
33
34
  expectedBalances.upsert(token, { token, balance });
34
35
  const decimals = this.sdk.tokensMeta.decimals(token);
35
36
  const minBalance = 10n ** BigInt(Math.max(8, decimals) - 8);
36
- if (balance < minBalance || !isEnabled) {
37
+ if (keepAssetsSet.has(token.toLowerCase()) || balance < minBalance || !isEnabled) {
37
38
  leftoverBalances.upsert(token, { token, balance });
38
39
  }
39
40
  }
@@ -240,11 +240,12 @@ class RouterV300Contract extends AbstractRouterContract {
240
240
  /**
241
241
  * Implements {@link IRouterContract.getFindClosePathInput}
242
242
  */
243
- getFindClosePathInput(ca, cm, balances) {
243
+ getFindClosePathInput(ca, cm, balances, keepAssets) {
244
244
  const { expectedBalances, leftoverBalances } = this.getExpectedAndLeftover(
245
245
  ca,
246
246
  cm,
247
- balances
247
+ balances,
248
+ keepAssets
248
249
  );
249
250
  const pathOptions = PathOptionFactory.generatePathOptions(
250
251
  ca.tokens,
@@ -141,7 +141,13 @@ class RouterV310Contract extends AbstractRouterContract {
141
141
  * Implements {@link IRouterContract.findBestClosePath}
142
142
  */
143
143
  async findBestClosePath(props) {
144
- const { creditAccount: ca, creditManager: cm, slippage, balances } = props;
144
+ const {
145
+ creditAccount: ca,
146
+ creditManager: cm,
147
+ slippage,
148
+ balances,
149
+ keepAssets
150
+ } = props;
145
151
  const { expectedBalances, leftoverBalances, tokensToClaim } = this.getExpectedAndLeftover(
146
152
  ca,
147
153
  cm,
@@ -149,7 +155,8 @@ class RouterV310Contract extends AbstractRouterContract {
149
155
  expectedBalances: assetsMap(balances.expectedBalances),
150
156
  leftoverBalances: assetsMap(balances.leftoverBalances),
151
157
  tokensToClaim: assetsMap(balances.tokensToClaim || [])
152
- } : void 0
158
+ } : void 0,
159
+ keepAssets
153
160
  );
154
161
  const getNumSplits = this.#numSplitsGetter(cm, expectedBalances.values());
155
162
  const tData = [];
@@ -0,0 +1,24 @@
1
+ import { getAddress } from "viem";
2
+ class AddressSet extends Set {
3
+ constructor(entries) {
4
+ super(Array.from(entries ?? []).map((a) => getAddress(a)));
5
+ }
6
+ add(value) {
7
+ return super.add(getAddress(value));
8
+ }
9
+ delete(value) {
10
+ return super.delete(getAddress(value));
11
+ }
12
+ has(value) {
13
+ return super.has(getAddress(value));
14
+ }
15
+ asArray() {
16
+ return Array.from(this);
17
+ }
18
+ map(fn) {
19
+ return this.asArray().map(fn);
20
+ }
21
+ }
22
+ export {
23
+ AddressSet
24
+ };
@@ -1,4 +1,5 @@
1
1
  export * from "./AddressMap.js";
2
+ export * from "./AddressSet.js";
2
3
  export * from "./bytes32ToString.js";
3
4
  export * from "./childLogger.js";
4
5
  export * from "./createRawTx.js";
@@ -25,6 +25,10 @@ export interface TargetAccount {
25
25
  * TODO: not implemented
26
26
  */
27
27
  collateral?: Address;
28
+ /**
29
+ * These tokens will be transferred directly from faucet to credit account
30
+ */
31
+ directTransfer?: Address[];
28
32
  leverage?: number;
29
33
  slippage?: number;
30
34
  }
@@ -5,7 +5,7 @@ import type { GearboxSDK } from "../GearboxSDK.js";
5
5
  import type { OnDemandPriceUpdate, UpdatePriceFeedsResult } from "../market/index.js";
6
6
  import { type Asset, type RouterCASlice } from "../router/index.js";
7
7
  import type { MultiCall } from "../types/index.js";
8
- import type { GetConnectedBotsResult } from "./types";
8
+ import type { FullyLiquidateProps, GetConnectedBotsResult } from "./types";
9
9
  import type { AddCollateralProps, ChangeDeptProps, CloseCreditAccountProps, CloseCreditAccountResult, CreditAccountOperationResult, EnableTokensProps, ExecuteSwapProps, GetCreditAccountsOptions, OpenCAProps, PermitResult, PrepareUpdateQuotasProps, Rewards, UpdateQuotasProps } from "./types.js";
10
10
  export interface CreditAccountServiceOptions {
11
11
  batchSize?: number;
@@ -49,13 +49,10 @@ export declare abstract class AbstractCreditAccountService extends SDKConstruct
49
49
  }>): Promise<GetConnectedBotsResult>;
50
50
  /**
51
51
  * Generates transaction to liquidate credit account
52
- * @param account
53
- * @param to Address to transfer underlying left after liquidation
54
- * @param slippage
55
- * @param force TODO: legacy v3 option to remove
52
+ * @param props - {@link FullyLiquidateProps}
56
53
  * @returns
57
54
  */
58
- fullyLiquidate(account: RouterCASlice, to: Address, slippage?: bigint, force?: boolean): Promise<CloseCreditAccountResult>;
55
+ fullyLiquidate(props: FullyLiquidateProps): Promise<CloseCreditAccountResult>;
59
56
  /**
60
57
  * Closes credit account or closes credit account and keeps it open with zero debt.
61
58
  - Ca is closed in the following order: price update -> close path to swap all tokens into underlying ->
@@ -239,6 +239,28 @@ export interface ChangeDeptProps {
239
239
  */
240
240
  amount: bigint;
241
241
  }
242
+ export interface FullyLiquidateProps {
243
+ /**
244
+ * Credit account to liquidate
245
+ */
246
+ account: RouterCASlice;
247
+ /**
248
+ * Address to transfer underlying left after liquidation
249
+ */
250
+ to: Address;
251
+ /**
252
+ * Slippage in PERCENTAGE_FORMAT (100% = 10_000) per operation
253
+ */
254
+ slippage?: bigint;
255
+ /**
256
+ * TODO: legacy v3 option to remove
257
+ */
258
+ force?: boolean;
259
+ /**
260
+ * List of assets to keep on account after liquidation
261
+ */
262
+ keepAssets?: Address[];
263
+ }
242
264
  export interface PermitResult {
243
265
  r: Address;
244
266
  s: Address;
@@ -338,13 +360,10 @@ export interface ICreditAccountsService extends SDKConstruct {
338
360
  setBot: (props: SetBotProps) => Promise<CreditAccountOperationResult>;
339
361
  /**
340
362
  * Generates transaction to liquidate credit account
341
- * @param account
342
- * @param to Address to transfer underlying left after liquidation
343
- * @param slippage
344
- * @param force TODO: legacy v3 option to remove
363
+ * @param props - {@link FullyLiquidateProps}
345
364
  * @returns
346
365
  */
347
- fullyLiquidate(account: RouterCASlice, to: Address, slippage?: bigint, force?: boolean): Promise<CloseCreditAccountResult>;
366
+ fullyLiquidate(props: FullyLiquidateProps): Promise<CloseCreditAccountResult>;
348
367
  /**
349
368
  * Closes credit account or closes credit account and keeps it open with zero debt.
350
369
  * - Ca is closed in the following order: price update -> close path to swap all tokens into underlying ->
@@ -1,4 +1,4 @@
1
- import type { Abi } from "viem";
1
+ import type { Abi, Address } from "viem";
2
2
  import { BaseContract } from "../base/index.js";
3
3
  import { AddressMap } from "../utils/index.js";
4
4
  import type { IHooks } from "../utils/internal/index.js";
@@ -13,6 +13,6 @@ export declare abstract class AbstractRouterContract<abi extends Abi | readonly
13
13
  protected readonly hooks: Hooks<RouterHooks>;
14
14
  readonly addHook: <K extends "foundPathOptions">(hookName: K, fn: (...args: RouterHooks[K]) => void | Promise<void>) => void;
15
15
  readonly removeHook: <K extends "foundPathOptions">(hookName: K, fn: (...args: RouterHooks[K]) => void | Promise<void>) => void;
16
- protected getExpectedAndLeftover(ca: RouterCASlice, cm: RouterCMSlice, balances?: Leftovers): Leftovers;
17
- protected getDefaultExpectedAndLeftover(ca: RouterCASlice): Leftovers;
16
+ protected getExpectedAndLeftover(ca: RouterCASlice, cm: RouterCMSlice, balances?: Leftovers, keepAssets?: Address[]): Leftovers;
17
+ protected getDefaultExpectedAndLeftover(ca: RouterCASlice, keepAssets?: Address[]): Leftovers;
18
18
  }
@@ -31,7 +31,7 @@ export declare class RouterV300Contract extends AbstractRouterContract<abi> impl
31
31
  /**
32
32
  * Implements {@link IRouterContract.getFindClosePathInput}
33
33
  */
34
- getFindClosePathInput(ca: RouterCASlice, cm: RouterCMSlice, balances?: Leftovers): FindClosePathInput;
34
+ getFindClosePathInput(ca: RouterCASlice, cm: RouterCMSlice, balances?: Leftovers, keepAssets?: Address[]): FindClosePathInput;
35
35
  /**
36
36
  * Implements {@link IRouterContract.getAvailableConnectors}
37
37
  */
@@ -201,6 +201,11 @@ export interface FindBestClosePathProps {
201
201
  * Balances {@link ClosePathBalances} to close account with, if not provided, all assets will be swapped according to inner logic.
202
202
  */
203
203
  balances?: ClosePathBalances;
204
+ /**
205
+ * List of assets to keep on account after closing.
206
+ * When balances are explicitly provided, keepAssets is ignored.
207
+ */
208
+ keepAssets?: Address[];
204
209
  /**
205
210
  * TODO: legacy v3 option to pass to contract
206
211
  */
@@ -281,9 +286,10 @@ export interface IRouterContract extends IBaseContract {
281
286
  * @param ca
282
287
  * @param cm
283
288
  * @param balances
289
+ * @param keepAssets
284
290
  * @returns
285
291
  */
286
- getFindClosePathInput: (ca: RouterCASlice, cm: RouterCMSlice, balances?: Leftovers) => FindClosePathInput;
292
+ getFindClosePathInput: (ca: RouterCASlice, cm: RouterCMSlice, balances?: Leftovers, keepAssets?: Address[]) => FindClosePathInput;
287
293
  }
288
294
  export type RouterHooks = {
289
295
  /**
@@ -0,0 +1,9 @@
1
+ import { type Address } from "viem";
2
+ export declare class AddressSet extends Set<Address> {
3
+ constructor(entries?: Iterable<Address>);
4
+ add(value: Address): this;
5
+ delete(value: Address): boolean;
6
+ has(value: Address): boolean;
7
+ asArray(): Address[];
8
+ map<T>(fn: (address: Address) => T): T[];
9
+ }
@@ -1,4 +1,5 @@
1
1
  export * from "./AddressMap.js";
2
+ export * from "./AddressSet.js";
2
3
  export * from "./bytes32ToString.js";
3
4
  export * from "./childLogger.js";
4
5
  export * from "./createRawTx.js";
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@gearbox-protocol/sdk",
3
- "version": "8.8.1",
3
+ "version": "8.10.0",
4
4
  "description": "Gearbox SDK",
5
5
  "license": "MIT",
6
6
  "main": "./dist/cjs/sdk/index.js",