@suilend/sdk 12.3.0-next.gcbeb44e → 12.4.0-next.g7a809d2

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.
@@ -58,6 +58,7 @@ export interface ClosePoolRewardArgs {
58
58
  }
59
59
  export declare function closePoolReward(tx: Transaction, typeArgs: [string, string], args: ClosePoolRewardArgs): import("@mysten/sui/transactions").TransactionResult;
60
60
  export declare function reserve(tx: Transaction, typeArgs: [string, string], lendingMarket: TransactionObjectInput): import("@mysten/sui/transactions").TransactionResult;
61
+ export declare function reserveConfigBuilder(tx: Transaction, typeArgs: [string, string], lendingMarket: TransactionObjectInput): import("@mysten/sui/transactions").TransactionResult;
61
62
  export interface CompoundInterestArgs {
62
63
  lendingMarket: TransactionObjectInput;
63
64
  reserveArrayIndex: bigint | TransactionArgument;
@@ -107,6 +107,17 @@ export function reserve(tx, typeArgs, lendingMarket) {
107
107
  arguments: [obj(tx, lendingMarket)],
108
108
  });
109
109
  }
110
+ // lending_market::reserve_config_builder has no published package to
111
+ // regenerate a binding from yet, so this is hand-written to match the shape
112
+ // codegen already produces for `reserve` above. Replace it with the real
113
+ // generated output once this package publishes and the generator runs again.
114
+ export function reserveConfigBuilder(tx, typeArgs, lendingMarket) {
115
+ return tx.moveCall({
116
+ target: `${PUBLISHED_AT}::lending_market::reserve_config_builder`,
117
+ typeArguments: typeArgs,
118
+ arguments: [obj(tx, lendingMarket)],
119
+ });
120
+ }
110
121
  export function compoundInterest(tx, typeArg, args) {
111
122
  return tx.moveCall({
112
123
  target: `${PUBLISHED_AT}::lending_market::compound_interest`,
@@ -13,6 +13,11 @@ export interface SetSpreadFeeBpsArgs {
13
13
  spreadFeeBps: bigint | TransactionArgument;
14
14
  }
15
15
  export declare function setSpreadFeeBps(tx: Transaction, args: SetSpreadFeeBpsArgs): import("@mysten/sui/transactions").TransactionResult;
16
+ export interface SetConfidenceMultiplierBuilderArgs {
17
+ builder: TransactionObjectInput;
18
+ confidenceMultiplierBps: bigint | TransactionArgument;
19
+ }
20
+ export declare function setConfidenceMultiplierBuilder(tx: Transaction, args: SetConfidenceMultiplierBuilderArgs): import("@mysten/sui/transactions").TransactionResult;
16
21
  export declare function spreadFee(tx: Transaction, config: TransactionObjectInput): import("@mysten/sui/transactions").TransactionResult;
17
22
  export declare function borrowFee(tx: Transaction, config: TransactionObjectInput): import("@mysten/sui/transactions").TransactionResult;
18
23
  export declare function borrowLimit(tx: Transaction, config: TransactionObjectInput): import("@mysten/sui/transactions").TransactionResult;
@@ -29,6 +29,21 @@ export function setSpreadFeeBps(tx, args) {
29
29
  arguments: [obj(tx, args.builder), pure(tx, args.spreadFeeBps, `u64`)],
30
30
  });
31
31
  }
32
+ // reserve_config::set_confidence_multiplier_builder is published (v25), but
33
+ // this SDK's PUBLISHED_AT/PKG_V* constants haven't been bumped to v25 yet, so
34
+ // there's nothing to regenerate a binding from until that separate regen
35
+ // pass happens. Hand-written to match the shape codegen already produces for
36
+ // every other u64 builder setter in this file (e.g. setSpreadFeeBps above).
37
+ // Replace it with the real generated output once that regen lands.
38
+ export function setConfidenceMultiplierBuilder(tx, args) {
39
+ return tx.moveCall({
40
+ target: `${PUBLISHED_AT}::reserve_config::set_confidence_multiplier_builder`,
41
+ arguments: [
42
+ obj(tx, args.builder),
43
+ pure(tx, args.confidenceMultiplierBps, `u64`),
44
+ ],
45
+ });
46
+ }
32
47
  export function spreadFee(tx, config) {
33
48
  return tx.moveCall({
34
49
  target: `${PUBLISHED_AT}::reserve_config::spread_fee`,
package/client.d.ts CHANGED
@@ -1,13 +1,22 @@
1
1
  import { SuiClientTypes } from "@mysten/sui/client";
2
2
  import { SuiGrpcClient } from "@mysten/sui/grpc";
3
- import { Transaction, TransactionObjectArgument, TransactionObjectInput } from "@mysten/sui/transactions";
3
+ import { Transaction, TransactionArgument, TransactionObjectArgument, TransactionObjectInput } from "@mysten/sui/transactions";
4
4
  import { SuiPythClient } from "@pythnetwork/pyth-sui-js";
5
+ import BigNumber from "bignumber.js";
5
6
  import { LendingMarket, ObligationOwnerCap } from "./_generated/suilend/lending-market/structs";
6
7
  import { Obligation } from "./_generated/suilend/obligation/structs";
7
8
  import { NewConfigArgs as CreateRateLimiterConfigArgs } from "./_generated/suilend/rate-limiter/functions";
8
9
  import { CreateReserveConfigArgs } from "./_generated/suilend/reserve-config/functions";
9
10
  import { type PriceUpdateDataSource } from "./lib/pyth";
10
11
  import { Side } from "./lib/types";
12
+ /**
13
+ * Signed Pyth update data the caller already fetched for `priceIds`, posted
14
+ * unconditionally instead of the on-chain staleness check deciding.
15
+ */
16
+ export interface PriceUpdate {
17
+ priceIds: string[];
18
+ updateData: Buffer[];
19
+ }
11
20
  export declare function needsPriceRefresh(stalenessSeconds: number): boolean;
12
21
  export declare const ADMIN_ADDRESS: string;
13
22
  export declare const LENDING_MARKET_REGISTRY_ID: string;
@@ -41,6 +50,9 @@ export type ClaimRewardsReward = {
41
50
  rewardCoinType: string;
42
51
  side: Side;
43
52
  };
53
+ export type PatchReserveConfigArgs = Partial<CreateReserveConfigArgs> & {
54
+ confidenceMultiplierBps?: bigint | TransactionArgument;
55
+ };
44
56
  export declare class SuilendClient {
45
57
  lendingMarket: LendingMarket<string>;
46
58
  suiGrpcClient: SuiGrpcClient;
@@ -71,6 +83,8 @@ export declare class SuilendClient {
71
83
  receivers: string[];
72
84
  weights: string[];
73
85
  }>;
86
+ /** A reserve's confidence multiplier `k`, in bps; pass `parsedReserve.config.additionalFields.id`. 0 when unset. */
87
+ static getReserveConfidenceMultiplierBps(suiGrpcClient: SuiGrpcClient, additionalFieldsBagId: string): Promise<BigNumber>;
74
88
  static createNewLendingMarket(registryId: string, lendingMarketType: string, transaction: Transaction): {
75
89
  NestedResult: [number, number];
76
90
  $kind: "NestedResult";
@@ -94,12 +108,19 @@ export declare class SuilendClient {
94
108
  claimRewardsAndSendToUser(ownerId: string, obligationOwnerCap: TransactionObjectInput, rewards: ClaimRewardsReward[], transaction: Transaction): Transaction;
95
109
  claimRewardsAndDeposit(ownerId: string, obligationOwnerCap: TransactionObjectInput, rewards: ClaimRewardsReward[], transaction: Transaction): Transaction;
96
110
  findReserveArrayIndex(coinType: string): bigint;
111
+ private submitReserveConfigUpdate;
97
112
  updateReserveConfig(lendingMarketOwnerCapId: string, transaction: Transaction, coinType: string, createReserveConfigArgs: CreateReserveConfigArgs): import("@mysten/sui/transactions").TransactionResult;
113
+ /**
114
+ * Updates a subset of a reserve's config fields, leaving every unspecified
115
+ * field -- including `additionalFields` entries like the confidence
116
+ * multiplier -- unchanged from the reserve's current on-chain value.
117
+ */
118
+ patchReserveConfig(lendingMarketOwnerCapId: string, transaction: Transaction, coinType: string, overrides: PatchReserveConfigArgs): import("@mysten/sui/transactions").TransactionResult;
98
119
  newObligationOwnerCap(transaction: Transaction, lendingMarketOwnerCapId: string, destinationAddress: string, obligationId: string): void;
99
120
  updateRateLimiterConfig(lendingMarketOwnerCapId: string, transaction: Transaction, newRateLimiterConfigArgs: CreateRateLimiterConfigArgs): import("@mysten/sui/transactions").TransactionResult;
100
121
  changeReservePriceFeed(lendingMarketOwnerCapId: string, coinType: string, pythPriceId: string, transaction: Transaction): Promise<import("@mysten/sui/transactions").TransactionResult>;
101
122
  createObligation(transaction: Transaction): import("@mysten/sui/transactions").TransactionResult;
102
- refreshAll(transaction: Transaction, obligation?: Obligation<string>, coinTypes?: string[]): Promise<void>;
123
+ refreshAll(transaction: Transaction, obligation?: Obligation<string>, coinTypes?: string[], priceUpdate?: PriceUpdate): Promise<void>;
103
124
  refreshReservePrices(transaction: Transaction, priceInfoObjectId: string, reserveArrayIndex: bigint): Promise<void>;
104
125
  deposit(sendCoin: TransactionObjectInput, coinType: string, obligationOwnerCap: TransactionObjectInput, transaction: Transaction): void;
105
126
  depositIntoObligation(ownerId: string, coinType: string, value: string, transaction: Transaction, obligationOwnerCap: TransactionObjectInput): Promise<void>;
@@ -111,8 +132,8 @@ export declare class SuilendClient {
111
132
  borrowAndSendToUser(ownerId: string, obligationOwnerCap: TransactionObjectInput, obligationId: string, coinType: string, value: string, transaction: Transaction): Promise<void>;
112
133
  repay(obligationId: string, coinType: string, coin: TransactionObjectInput, transaction: Transaction): import("@mysten/sui/transactions").TransactionResult;
113
134
  repayIntoObligation(ownerId: string, obligationId: string, coinType: string, value: string, transaction: Transaction): Promise<import("@mysten/sui/transactions").TransactionResult>;
114
- liquidateAndRedeem(transaction: Transaction, obligation: Obligation<string>, repayCoinType: string, withdrawCoinType: string, repayCoinId: TransactionObjectInput): Promise<import("@mysten/sui/transactions").TransactionResult>;
115
- liquidate(transaction: Transaction, obligation: Obligation<string>, repayCoinType: string, withdrawCoinType: string, repayCoinId: TransactionObjectInput): Promise<import("@mysten/sui/transactions").TransactionResult>;
135
+ liquidateAndRedeem(transaction: Transaction, obligation: Obligation<string>, repayCoinType: string, withdrawCoinType: string, repayCoinId: TransactionObjectInput, priceUpdate?: PriceUpdate): Promise<import("@mysten/sui/transactions").TransactionResult>;
136
+ liquidate(transaction: Transaction, obligation: Obligation<string>, repayCoinType: string, withdrawCoinType: string, repayCoinId: TransactionObjectInput, priceUpdate?: PriceUpdate): Promise<import("@mysten/sui/transactions").TransactionResult>;
116
137
  migrate(transaction: Transaction, lendingMarketOwnerCapId: string): import("@mysten/sui/transactions").TransactionResult;
117
138
  claimFees(transaction: Transaction, coinType: string): import("@mysten/sui/transactions").TransactionResult;
118
139
  setFeeReceiversAndWeights(transaction: Transaction, lendingMarketOwnerCapId: string, receivers: string[], weights: bigint[]): import("@mysten/sui/transactions").TransactionResult;
package/client.js CHANGED
@@ -1,15 +1,17 @@
1
+ import { bcs } from "@mysten/sui/bcs";
1
2
  import { SUI_CLOCK_OBJECT_ID, SUI_SYSTEM_STATE_OBJECT_ID, normalizeStructTag, normalizeSuiObjectId, toHex, } from "@mysten/sui/utils";
2
3
  import { SuiPythClient } from "@pythnetwork/pyth-sui-js";
4
+ import BigNumber from "bignumber.js";
3
5
  import { extractCTokenCoinType, getAllCoins, getSpendableCoin, } from "@suilend/sui-core";
4
6
  import { phantom } from "./_generated/_framework/reified.js";
5
7
  import { PKG_V10, setPublishedAt } from "./_generated/suilend/index.js";
6
8
  import { PACKAGE_ID, PUBLISHED_AT } from "./_generated/suilend/index.js";
7
- import { addPoolReward, borrowRequest, cancelPoolReward, claimFees, claimRewards, claimRewardsAndDeposit, closePoolReward, depositCtokensIntoObligation, depositLiquidityAndMintCtokens, fulfillLiquidityRequest, liquidate, migrate, newObligationOwnerCap, rebalanceStaker, redeemCtokensAndWithdrawLiquidityRequest, repay, setFeeReceivers, unstakeSuiFromStaker, updateRateLimiterConfig, updateReserveConfig, withdrawCtokens, } from "./_generated/suilend/lending-market/functions.js";
9
+ import { addPoolReward, borrowRequest, cancelPoolReward, claimFees, claimRewards, claimRewardsAndDeposit, closePoolReward, depositCtokensIntoObligation, depositLiquidityAndMintCtokens, fulfillLiquidityRequest, liquidate, migrate, newObligationOwnerCap, rebalanceStaker, redeemCtokensAndWithdrawLiquidityRequest, repay, reserveConfigBuilder, setFeeReceivers, unstakeSuiFromStaker, updateRateLimiterConfig, updateReserveConfig, withdrawCtokens, } from "./_generated/suilend/lending-market/functions.js";
8
10
  import { LendingMarket, ObligationOwnerCap, } from "./_generated/suilend/lending-market/structs.js";
9
11
  import { createLendingMarket } from "./_generated/suilend/lending-market-registry/functions.js";
10
12
  import { Obligation } from "./_generated/suilend/obligation/structs.js";
11
13
  import { newConfig as createRateLimiterConfig, } from "./_generated/suilend/rate-limiter/functions.js";
12
- import { createReserveConfig, } from "./_generated/suilend/reserve-config/functions.js";
14
+ import { build as buildReserveConfig, createReserveConfig, setBorrowFeeBps, setBorrowLimit, setBorrowLimitUsd, setBorrowWeightBps, setCloseAttributedBorrowLimitUsd, setCloseLtvPct, setConfidenceMultiplierBuilder, setDepositLimit, setDepositLimitUsd, setInterestRateAprs, setInterestRateUtils, setIsolated, setLiquidationBonusBps, setMaxCloseLtvPct, setMaxLiquidationBonusBps, setOpenAttributedBorrowLimitUsd, setOpenLtvPct, setProtocolLiquidationFeeBps, setSpreadFeeBps, } from "./_generated/suilend/reserve-config/functions.js";
13
15
  import { addReserveProCompatible, addReserveV2ProCompatible, changeReservePriceFeedProCompatible, priceInfoPublishTime, refreshReservePriceProCompatible, } from "./lib/proCompatible.js";
14
16
  import { resolvePriceUpdateDataSource, } from "./lib/pyth.js";
15
17
  import { Side } from "./lib/types.js";
@@ -28,6 +30,7 @@ const isSui = (coinType) => normalizeStructTag(coinType) === NORMALIZED_SUI_COIN
28
30
  // refresh would come from a package the contract does not accept.
29
31
  const WORMHOLE_STATE_ID = "0xdbca52b9fb4f712e25f61f974586d93ac541bcf8389564f0323bb07215168b5c";
30
32
  const PYTH_STATE_ID = "0x03719fae774ddab3cfcaa53bbc046f0cbe21410019b6280811bf3f9f4b05839d";
33
+ const normalizePriceIdentifier = (id) => id.toLowerCase().replace(/^0x/, "");
31
34
  // Mirrors suilend::oracles's MAX_FORWARD_SECONDS (move/contracts/suilend/sources/oracles.move);
32
35
  // `refresh_reserve_price` now rejects a forward-dated price the same as a stale one.
33
36
  const MAX_FORWARD_SECONDS = 60;
@@ -153,6 +156,87 @@ export async function listAllOwnedObjects(suiGrpcClient, owner, type, include) {
153
156
  cache.set(key, promise);
154
157
  return promise;
155
158
  }
159
+ // One entry per CreateReserveConfigArgs field, each just closing over its
160
+ // generated setter -- lets patchReserveConfig apply only the caller's
161
+ // specified overrides with one loop instead of one copy-pasted
162
+ // `if (overrides.x !== undefined) setX(...)` per field.
163
+ const CREATE_RESERVE_CONFIG_FIELD_SETTERS = {
164
+ openLtvPct: (tx, builder, openLtvPct) => setOpenLtvPct(tx, { builder, openLtvPct }),
165
+ closeLtvPct: (tx, builder, closeLtvPct) => setCloseLtvPct(tx, { builder, closeLtvPct }),
166
+ maxCloseLtvPct: (tx, builder, maxCloseLtvPct) => setMaxCloseLtvPct(tx, { builder, maxCloseLtvPct }),
167
+ borrowWeightBps: (tx, builder, borrowWeightBps) => setBorrowWeightBps(tx, { builder, borrowWeightBps }),
168
+ depositLimit: (tx, builder, depositLimit) => setDepositLimit(tx, { builder, depositLimit }),
169
+ borrowLimit: (tx, builder, borrowLimit) => setBorrowLimit(tx, { builder, borrowLimit }),
170
+ liquidationBonusBps: (tx, builder, liquidationBonusBps) => setLiquidationBonusBps(tx, { builder, liquidationBonusBps }),
171
+ maxLiquidationBonusBps: (tx, builder, maxLiquidationBonusBps) => setMaxLiquidationBonusBps(tx, { builder, maxLiquidationBonusBps }),
172
+ depositLimitUsd: (tx, builder, depositLimitUsd) => setDepositLimitUsd(tx, { builder, depositLimitUsd }),
173
+ borrowLimitUsd: (tx, builder, borrowLimitUsd) => setBorrowLimitUsd(tx, { builder, borrowLimitUsd }),
174
+ borrowFeeBps: (tx, builder, borrowFeeBps) => setBorrowFeeBps(tx, { builder, borrowFeeBps }),
175
+ spreadFeeBps: (tx, builder, spreadFeeBps) => setSpreadFeeBps(tx, { builder, spreadFeeBps }),
176
+ protocolLiquidationFeeBps: (tx, builder, protocolLiquidationFeeBps) => setProtocolLiquidationFeeBps(tx, { builder, protocolLiquidationFeeBps }),
177
+ interestRateUtils: (tx, builder, interestRateUtils) => setInterestRateUtils(tx, { builder, interestRateUtils }),
178
+ interestRateAprs: (tx, builder, interestRateAprs) => setInterestRateAprs(tx, { builder, interestRateAprs }),
179
+ isolated: (tx, builder, isolated) => setIsolated(tx, { builder, isolated }),
180
+ openAttributedBorrowLimitUsd: (tx, builder, openAttributedBorrowLimitUsd) => setOpenAttributedBorrowLimitUsd(tx, {
181
+ builder,
182
+ openAttributedBorrowLimitUsd,
183
+ }),
184
+ closeAttributedBorrowLimitUsd: (tx, builder, closeAttributedBorrowLimitUsd) => setCloseAttributedBorrowLimitUsd(tx, {
185
+ builder,
186
+ closeAttributedBorrowLimitUsd,
187
+ }),
188
+ };
189
+ // A generic type parameter, unlike indexing CREATE_RESERVE_CONFIG_FIELD_SETTERS
190
+ // with the union `keyof CreateReserveConfigArgs` directly at the call site,
191
+ // keeps TS from expanding the call into one union branch per field (which
192
+ // blows up into "union type too complex to represent").
193
+ function applyCreateReserveConfigFieldOverride(tx, builder, overrides, key) {
194
+ const value = overrides[key];
195
+ if (value === undefined)
196
+ return;
197
+ // TS can't verify this call is sound (calling a mapped type indexed by a
198
+ // generic key produces a union of all 18 setter signatures, which blows up
199
+ // into "union type too complex to represent" rather than typechecking) --
200
+ // but it is: `value`'s type and CREATE_RESERVE_CONFIG_FIELD_SETTERS[key]'s
201
+ // parameter type are both CreateReserveConfigArgs[K] for the same K.
202
+ CREATE_RESERVE_CONFIG_FIELD_SETTERS[key](tx, builder, value);
203
+ }
204
+ // Only safe for a key with a fixed, known type tag; see findBagEntryByTypeSuffix
205
+ // for one that can legitimately be absent.
206
+ async function resolveDynamicFieldId(suiGrpcClient, parentId, nameType) {
207
+ const { dynamicField } = await suiGrpcClient.getDynamicField({
208
+ parentId,
209
+ name: { type: nameType, bcs: new Uint8Array([0]) },
210
+ });
211
+ return dynamicField.fieldId;
212
+ }
213
+ // For a key that's always present (getFeeReceivers), both calls' errors can
214
+ // propagate through this combined helper unguarded.
215
+ async function fetchDynamicFieldJson(suiGrpcClient, parentId, nameType) {
216
+ const fieldId = await resolveDynamicFieldId(suiGrpcClient, parentId, nameType);
217
+ const { object } = await suiGrpcClient.getObject({
218
+ objectId: fieldId,
219
+ include: { json: true },
220
+ });
221
+ return object.json;
222
+ }
223
+ // Scans by type-tag suffix, ignoring the package address: codegen doesn't
224
+ // track a struct's originally-defining address, only the current PUBLISHED_AT.
225
+ async function findBagEntryByTypeSuffix(suiGrpcClient, parentId, typeSuffix) {
226
+ let cursor = null;
227
+ while (true) {
228
+ const response = await suiGrpcClient.listDynamicFields({ parentId, cursor });
229
+ const match = response.dynamicFields.find((entry) => entry.name.type.endsWith(typeSuffix));
230
+ if (match)
231
+ return match;
232
+ if (!response.hasNextPage)
233
+ return undefined;
234
+ if (!response.cursor) {
235
+ throw new Error(`listDynamicFields for ${parentId} reported hasNextPage=true with no cursor to continue`);
236
+ }
237
+ cursor = response.cursor;
238
+ }
239
+ }
156
240
  export class SuilendClient {
157
241
  lendingMarket;
158
242
  suiGrpcClient;
@@ -249,22 +333,22 @@ export class SuilendClient {
249
333
  });
250
334
  }
251
335
  static async getFeeReceivers(suiGrpcClient, lendingMarketId) {
252
- const { dynamicField } = await suiGrpcClient.getDynamicField({
253
- parentId: lendingMarketId,
254
- name: {
255
- type: `${PKG_V10}::lending_market::FeeReceiversKey`,
256
- bcs: new Uint8Array([0]),
257
- },
258
- });
259
- const { object } = await suiGrpcClient.getObject({
260
- objectId: dynamicField.fieldId,
261
- include: { json: true },
262
- });
263
- const json = object.json;
336
+ const json = await fetchDynamicFieldJson(suiGrpcClient, lendingMarketId, `${PKG_V10}::lending_market::FeeReceiversKey`);
264
337
  const receivers = json?.value?.receivers;
265
338
  const weights = json?.value?.weights;
266
339
  return { receivers, weights };
267
340
  }
341
+ /** A reserve's confidence multiplier `k`, in bps; pass `parsedReserve.config.additionalFields.id`. 0 when unset. */
342
+ static async getReserveConfidenceMultiplierBps(suiGrpcClient, additionalFieldsBagId) {
343
+ const match = await findBagEntryByTypeSuffix(suiGrpcClient, additionalFieldsBagId, "::reserve_config::ConfidenceMultiplierKey");
344
+ if (!match)
345
+ return new BigNumber(0);
346
+ const { dynamicField } = await suiGrpcClient.getDynamicField({
347
+ parentId: additionalFieldsBagId,
348
+ name: match.name,
349
+ });
350
+ return new BigNumber(bcs.u64().parse(dynamicField.value.bcs));
351
+ }
268
352
  static createNewLendingMarket(registryId, lendingMarketType, transaction) {
269
353
  const [ownerCap, lendingMarket] = createLendingMarket(transaction, lendingMarketType, transaction.object(registryId));
270
354
  transaction.moveCall({
@@ -429,8 +513,7 @@ export class SuilendClient {
429
513
  const arrayIndex = this.lendingMarket.reserves.findIndex((r) => normalizeStructTag(r.coinType.name) === normalizeStructTag(coinType));
430
514
  return BigInt(arrayIndex);
431
515
  }
432
- updateReserveConfig(lendingMarketOwnerCapId, transaction, coinType, createReserveConfigArgs) {
433
- const [config] = createReserveConfig(transaction, createReserveConfigArgs);
516
+ submitReserveConfigUpdate(lendingMarketOwnerCapId, transaction, coinType, config) {
434
517
  return updateReserveConfig(transaction, [this.lendingMarket.$typeArgs[0], coinType], {
435
518
  lendingMarketOwnerCap: transaction.object(lendingMarketOwnerCapId),
436
519
  lendingMarket: transaction.object(this.lendingMarket.id),
@@ -438,6 +521,28 @@ export class SuilendClient {
438
521
  config: transaction.object(config),
439
522
  });
440
523
  }
524
+ updateReserveConfig(lendingMarketOwnerCapId, transaction, coinType, createReserveConfigArgs) {
525
+ const [config] = createReserveConfig(transaction, createReserveConfigArgs);
526
+ return this.submitReserveConfigUpdate(lendingMarketOwnerCapId, transaction, coinType, config);
527
+ }
528
+ /**
529
+ * Updates a subset of a reserve's config fields, leaving every unspecified
530
+ * field -- including `additionalFields` entries like the confidence
531
+ * multiplier -- unchanged from the reserve's current on-chain value.
532
+ */
533
+ patchReserveConfig(lendingMarketOwnerCapId, transaction, coinType, overrides) {
534
+ const [builder] = reserveConfigBuilder(transaction, [this.lendingMarket.$typeArgs[0], coinType], transaction.object(this.lendingMarket.id));
535
+ for (const key of Object.keys(CREATE_RESERVE_CONFIG_FIELD_SETTERS)) {
536
+ applyCreateReserveConfigFieldOverride(transaction, builder, overrides, key);
537
+ }
538
+ if (overrides.confidenceMultiplierBps !== undefined)
539
+ setConfidenceMultiplierBuilder(transaction, {
540
+ builder,
541
+ confidenceMultiplierBps: overrides.confidenceMultiplierBps,
542
+ });
543
+ const [config] = buildReserveConfig(transaction, builder);
544
+ return this.submitReserveConfigUpdate(lendingMarketOwnerCapId, transaction, coinType, config);
545
+ }
441
546
  newObligationOwnerCap(transaction, lendingMarketOwnerCapId, destinationAddress, obligationId) {
442
547
  const [obligationOwnerCap] = newObligationOwnerCap(transaction, this.lendingMarket.$typeArgs[0], {
443
548
  lendingMarketOwnerCap: transaction.object(lendingMarketOwnerCapId),
@@ -475,7 +580,7 @@ export class SuilendClient {
475
580
  typeArguments: this.lendingMarket.$typeArgs,
476
581
  });
477
582
  }
478
- async refreshAll(transaction, obligation, coinTypes) {
583
+ async refreshAll(transaction, obligation, coinTypes, priceUpdate) {
479
584
  const reserveArrayIndexToPriceId = new Map();
480
585
  if (obligation) {
481
586
  obligation.deposits.forEach((deposit) => {
@@ -505,8 +610,14 @@ export class SuilendClient {
505
610
  const priceInfoObjectId = await this.pythClient.getPriceFeedObjectId(priceIdentifiers[i]);
506
611
  priceInfoObjectIds[i] = priceInfoObjectId;
507
612
  })()));
508
- const stalePriceIdentifiers = [];
509
- await Promise.all(Object.entries(priceInfoObjectIds).map(([i, priceInfoObjectId]) => (async () => {
613
+ const suppliedPriceIdentifiers = new Set(priceUpdate?.priceIds.map(normalizePriceIdentifier));
614
+ if (priceUpdate?.priceIds.length) {
615
+ await this.pythClient.updatePriceFeeds(transaction, priceUpdate.updateData, priceUpdate.priceIds);
616
+ }
617
+ const stalePriceIdentifiers = new Set();
618
+ await Promise.all(Object.entries(priceInfoObjectIds)
619
+ .filter(([i]) => !suppliedPriceIdentifiers.has(normalizePriceIdentifier(priceIdentifiers[+i])))
620
+ .map(([i, priceInfoObjectId]) => (async () => {
510
621
  // Read the field directly: the generated parser's type check is
511
622
  // bound to one Pyth package address and would throw post-cutover.
512
623
  const { object } = await this.suiGrpcClient.getObject({
@@ -516,15 +627,16 @@ export class SuilendClient {
516
627
  const publishTime = priceInfoPublishTime(object.json, priceInfoObjectId);
517
628
  const stalenessSeconds = Date.now() / 1000 - Number(publishTime);
518
629
  if (needsPriceRefresh(stalenessSeconds)) {
519
- stalePriceIdentifiers.push(priceIdentifiers[+i]);
630
+ stalePriceIdentifiers.add(priceIdentifiers[+i]);
520
631
  }
521
632
  })()));
522
- if (stalePriceIdentifiers.length > 0) {
523
- const stalePriceUpdateData = await this.pythConnection.getPriceFeedsUpdateData(stalePriceIdentifiers);
524
- await this.pythClient.updatePriceFeeds(transaction, stalePriceUpdateData, stalePriceIdentifiers);
633
+ if (stalePriceIdentifiers.size > 0) {
634
+ const staleIds = [...stalePriceIdentifiers];
635
+ const stalePriceUpdateData = await this.pythConnection.getPriceFeedsUpdateData(staleIds);
636
+ await this.pythClient.updatePriceFeeds(transaction, stalePriceUpdateData, staleIds);
525
637
  }
526
638
  for (let i = 0; i < reserveArrayIndexes.length; i++) {
527
- this.refreshReservePrices(transaction, priceInfoObjectIds[i], reserveArrayIndexes[i]);
639
+ await this.refreshReservePrices(transaction, priceInfoObjectIds[i], reserveArrayIndexes[i]);
528
640
  }
529
641
  }
530
642
  async refreshReservePrices(transaction, priceInfoObjectId, reserveArrayIndex) {
@@ -670,8 +782,8 @@ export class SuilendClient {
670
782
  transaction.transferObjects([sendCoin], transaction.pure.address(ownerId));
671
783
  return result;
672
784
  }
673
- async liquidateAndRedeem(transaction, obligation, repayCoinType, withdrawCoinType, repayCoinId) {
674
- const [ctokens, exemption] = await this.liquidate(transaction, obligation, repayCoinType, withdrawCoinType, repayCoinId);
785
+ async liquidateAndRedeem(transaction, obligation, repayCoinType, withdrawCoinType, repayCoinId, priceUpdate) {
786
+ const [ctokens, exemption] = await this.liquidate(transaction, obligation, repayCoinType, withdrawCoinType, repayCoinId, priceUpdate);
675
787
  const [optionalExemption] = transaction.moveCall({
676
788
  target: `0x1::option::some`,
677
789
  typeArguments: [
@@ -681,8 +793,8 @@ export class SuilendClient {
681
793
  });
682
794
  return this.redeem(ctokens, withdrawCoinType, optionalExemption, transaction);
683
795
  }
684
- async liquidate(transaction, obligation, repayCoinType, withdrawCoinType, repayCoinId) {
685
- await this.refreshAll(transaction, obligation);
796
+ async liquidate(transaction, obligation, repayCoinType, withdrawCoinType, repayCoinId, priceUpdate) {
797
+ await this.refreshAll(transaction, obligation, undefined, priceUpdate);
686
798
  return liquidate(transaction, [this.lendingMarket.$typeArgs[0], repayCoinType, withdrawCoinType], {
687
799
  lendingMarket: transaction.object(this.lendingMarket.id),
688
800
  obligationId: obligation.id,
@@ -167,6 +167,7 @@ export declare const initializeSuilendAll: (suiGrpcClient: SuiGrpcClient, market
167
167
  isolated: boolean;
168
168
  openAttributedBorrowLimitUsd: number;
169
169
  closeAttributedBorrowLimitUsd: number;
170
+ additionalFields: import("../_generated/_dependencies/source/0x2/bag/structs").Bag;
170
171
  interestRate: {
171
172
  id: string;
172
173
  utilPercent: BigNumber;
@@ -306,6 +307,7 @@ export declare const initializeSuilendAll: (suiGrpcClient: SuiGrpcClient, market
306
307
  isolated: boolean;
307
308
  openAttributedBorrowLimitUsd: number;
308
309
  closeAttributedBorrowLimitUsd: number;
310
+ additionalFields: import("../_generated/_dependencies/source/0x2/bag/structs").Bag;
309
311
  interestRate: {
310
312
  id: string;
311
313
  utilPercent: BigNumber;
@@ -423,6 +425,7 @@ export declare const initializeSuilend: (suiGrpcClient: SuiGrpcClient, suilendCl
423
425
  isolated: boolean;
424
426
  openAttributedBorrowLimitUsd: number;
425
427
  closeAttributedBorrowLimitUsd: number;
428
+ additionalFields: import("../_generated/_dependencies/source/0x2/bag/structs").Bag;
426
429
  interestRate: {
427
430
  id: string;
428
431
  utilPercent: BigNumber;
@@ -562,6 +565,7 @@ export declare const initializeSuilend: (suiGrpcClient: SuiGrpcClient, suilendCl
562
565
  isolated: boolean;
563
566
  openAttributedBorrowLimitUsd: number;
564
567
  closeAttributedBorrowLimitUsd: number;
568
+ additionalFields: import("../_generated/_dependencies/source/0x2/bag/structs").Bag;
565
569
  interestRate: {
566
570
  id: string;
567
571
  utilPercent: BigNumber;
@@ -708,6 +712,7 @@ export declare const initializeObligations: (suiGrpcClient: SuiGrpcClient, suile
708
712
  isolated: boolean;
709
713
  openAttributedBorrowLimitUsd: number;
710
714
  closeAttributedBorrowLimitUsd: number;
715
+ additionalFields: import("../_generated/_dependencies/source/0x2/bag/structs").Bag;
711
716
  interestRate: {
712
717
  id: string;
713
718
  utilPercent: BigNumber;
@@ -825,6 +830,7 @@ export declare const initializeObligations: (suiGrpcClient: SuiGrpcClient, suile
825
830
  isolated: boolean;
826
831
  openAttributedBorrowLimitUsd: number;
827
832
  closeAttributedBorrowLimitUsd: number;
833
+ additionalFields: import("../_generated/_dependencies/source/0x2/bag/structs").Bag;
828
834
  interestRate: {
829
835
  id: string;
830
836
  utilPercent: BigNumber;
@@ -966,6 +972,7 @@ export declare const initializeObligations: (suiGrpcClient: SuiGrpcClient, suile
966
972
  isolated: boolean;
967
973
  openAttributedBorrowLimitUsd: number;
968
974
  closeAttributedBorrowLimitUsd: number;
975
+ additionalFields: import("../_generated/_dependencies/source/0x2/bag/structs").Bag;
969
976
  interestRate: {
970
977
  id: string;
971
978
  utilPercent: BigNumber;
@@ -1083,6 +1090,7 @@ export declare const initializeObligations: (suiGrpcClient: SuiGrpcClient, suile
1083
1090
  isolated: boolean;
1084
1091
  openAttributedBorrowLimitUsd: number;
1085
1092
  closeAttributedBorrowLimitUsd: number;
1093
+ additionalFields: import("../_generated/_dependencies/source/0x2/bag/structs").Bag;
1086
1094
  interestRate: {
1087
1095
  id: string;
1088
1096
  utilPercent: BigNumber;
package/package.json CHANGED
@@ -1 +1 @@
1
- {"name":"@suilend/sdk","version":"12.3.0-next.gcbeb44e","private":false,"description":"A TypeScript SDK for interacting with the Suilend program","author":"Suilend","license":"MIT","main":"./index.js","exports":{".":"./index.js","./client":"./client.js","./mmt":"./mmt.js","./strategies":"./strategies.js","./api/events":"./api/events.js","./api":"./api/index.js","./lib/constants":"./lib/constants.js","./lib":"./lib/index.js","./lib/initialize":"./lib/initialize.js","./lib/liquidityMining":"./lib/liquidityMining.js","./lib/proCompatible":"./lib/proCompatible.js","./lib/pyth":"./lib/pyth.js","./lib/strategyOwnerCap":"./lib/strategyOwnerCap.js","./lib/transactions":"./lib/transactions.js","./lib/types":"./lib/types.js","./margin":"./margin/index.js","./parsers/apiReserveAssetDataEvent":"./parsers/apiReserveAssetDataEvent.js","./parsers":"./parsers/index.js","./parsers/lendingMarket":"./parsers/lendingMarket.js","./parsers/obligation":"./parsers/obligation.js","./parsers/rateLimiter":"./parsers/rateLimiter.js","./parsers/reserve":"./parsers/reserve.js","./swap":"./swap/index.js","./swap/quote":"./swap/quote.js","./swap/transaction":"./swap/transaction.js","./utils/events":"./utils/events.js","./utils/feedId":"./utils/feedId.js","./utils":"./utils/index.js","./utils/obligation":"./utils/obligation.js","./utils/simulate":"./utils/simulate.js","./_generated/_framework/reified":"./_generated/_framework/reified.js","./_generated/_framework/util":"./_generated/_framework/util.js","./_generated/_framework/vector":"./_generated/_framework/vector.js","./_generated/suilend":"./_generated/suilend/index.js","./margin/margin/admin_cap":"./margin/margin/admin_cap.js","./margin/margin/market":"./margin/margin/market.js","./margin/margin/permissions":"./margin/margin/permissions.js","./margin/margin/position":"./margin/margin/position.js","./margin/margin/router":"./margin/margin/router.js","./margin/margin/version":"./margin/margin/version.js","./margin/utils":"./margin/utils/index.js","./_generated/suilend/cell/structs":"./_generated/suilend/cell/structs.js","./_generated/suilend/decimal/structs":"./_generated/suilend/decimal/structs.js","./_generated/suilend/lending-market/functions":"./_generated/suilend/lending-market/functions.js","./_generated/suilend/lending-market/structs":"./_generated/suilend/lending-market/structs.js","./_generated/suilend/lending-market-registry/functions":"./_generated/suilend/lending-market-registry/functions.js","./_generated/suilend/liquidity-mining/structs":"./_generated/suilend/liquidity-mining/structs.js","./_generated/suilend/obligation/structs":"./_generated/suilend/obligation/structs.js","./_generated/suilend/rate-limiter/functions":"./_generated/suilend/rate-limiter/functions.js","./_generated/suilend/rate-limiter/structs":"./_generated/suilend/rate-limiter/structs.js","./_generated/suilend/reserve/structs":"./_generated/suilend/reserve/structs.js","./_generated/suilend/reserve-config/functions":"./_generated/suilend/reserve-config/functions.js","./_generated/suilend/reserve-config/structs":"./_generated/suilend/reserve-config/structs.js","./_generated/_dependencies/source/0x1":"./_generated/_dependencies/source/0x1/index.js","./_generated/_dependencies/source/0x2":"./_generated/_dependencies/source/0x2/index.js","./_generated/_dependencies/source/0x8d97f1cd6ac663735be08d1d2b6d02a159e711586461306ce60a2b7a6a565a9e":"./_generated/_dependencies/source/0x8d97f1cd6ac663735be08d1d2b6d02a159e711586461306ce60a2b7a6a565a9e/index.js","./margin/margin/deps/std/type_name":"./margin/margin/deps/std/type_name.js","./margin/margin/deps/sui/vec_set":"./margin/margin/deps/sui/vec_set.js","./margin/margin/deps/suilend/lending_market":"./margin/margin/deps/suilend/lending_market.js","./_generated/_dependencies/source/0x1/ascii/structs":"./_generated/_dependencies/source/0x1/ascii/structs.js","./_generated/_dependencies/source/0x1/option/structs":"./_generated/_dependencies/source/0x1/option/structs.js","./_generated/_dependencies/source/0x1/type-name/structs":"./_generated/_dependencies/source/0x1/type-name/structs.js","./_generated/_dependencies/source/0x2/bag/structs":"./_generated/_dependencies/source/0x2/bag/structs.js","./_generated/_dependencies/source/0x2/balance/structs":"./_generated/_dependencies/source/0x2/balance/structs.js","./_generated/_dependencies/source/0x2/object/structs":"./_generated/_dependencies/source/0x2/object/structs.js","./_generated/_dependencies/source/0x2/object-table/structs":"./_generated/_dependencies/source/0x2/object-table/structs.js","./_generated/_dependencies/source/0x8d97f1cd6ac663735be08d1d2b6d02a159e711586461306ce60a2b7a6a565a9e/price-identifier/structs":"./_generated/_dependencies/source/0x8d97f1cd6ac663735be08d1d2b6d02a159e711586461306ce60a2b7a6a565a9e/price-identifier/structs.js"},"types":"./index.d.ts","scripts":{"build":"rm -rf ./dist && tsc && node ./fix-esm-imports.js","typecheck":"tsc --noEmit && tsc --noEmit -p tsconfig.test.json","test":"vitest run","lint:ci":"yarn run typecheck","prettier":"prettier --write src/ tests/","release":"yarn run build && node ./release.js && cd ./dist && npm publish --access public"},"repository":{"type":"git","url":"git+https://github.com/fireflyprotocol/lending-mono.git","directory":"ts/sdks/sdk"},"dependencies":{"@bluefin-exchange/bluefin7k-aggregator-sdk":"7.6.3","@cetusprotocol/aggregator-sdk":"1.7.2","@flowx-finance/sdk":"2.1.0","@pythnetwork/hermes-client":"3.1.0","@pythnetwork/pyth-sui-js":"4.0.0","@suilend/springsui-sdk":"4.1.0","bignumber.js":"11.1.5","bn.js":"5.2.5","crypto-js":"4.2.0","lodash":"4.18.1","p-limit":"7.3.2","uuid":"14.0.2"},"devDependencies":{"@mysten/bcs":"2.1.1","@mysten/sui":"2.29.0","@suilend/sui-core":"2.1.0","@tsconfig/recommended":"1.0.13","@types/bn.js":"5.2.0","@types/lodash":"4.17.25","@types/node":"26.5.1","fast-check":"4.10.0","prettier":"3.9.6","ts-node":"10.9.2","typescript":"6.0.3","vite":"8.3.0","vitest":"5.0.0"},"peerDependencies":{"@mysten/bcs":"^2.1.1","@mysten/sui":">=2.22.1 <3","@suilend/sui-core":"^2.0.1"},"type":"module"}
1
+ {"name":"@suilend/sdk","version":"12.4.0-next.g7a809d2","private":false,"description":"A TypeScript SDK for interacting with the Suilend program","author":"Suilend","license":"MIT","main":"./index.js","exports":{".":"./index.js","./client":"./client.js","./mmt":"./mmt.js","./strategies":"./strategies.js","./api/events":"./api/events.js","./api":"./api/index.js","./lib/constants":"./lib/constants.js","./lib":"./lib/index.js","./lib/initialize":"./lib/initialize.js","./lib/liquidityMining":"./lib/liquidityMining.js","./lib/proCompatible":"./lib/proCompatible.js","./lib/pyth":"./lib/pyth.js","./lib/strategyOwnerCap":"./lib/strategyOwnerCap.js","./lib/transactions":"./lib/transactions.js","./lib/types":"./lib/types.js","./margin":"./margin/index.js","./parsers/apiReserveAssetDataEvent":"./parsers/apiReserveAssetDataEvent.js","./parsers":"./parsers/index.js","./parsers/lendingMarket":"./parsers/lendingMarket.js","./parsers/obligation":"./parsers/obligation.js","./parsers/rateLimiter":"./parsers/rateLimiter.js","./parsers/reserve":"./parsers/reserve.js","./swap":"./swap/index.js","./swap/quote":"./swap/quote.js","./swap/transaction":"./swap/transaction.js","./utils/events":"./utils/events.js","./utils/feedId":"./utils/feedId.js","./utils":"./utils/index.js","./utils/obligation":"./utils/obligation.js","./utils/simulate":"./utils/simulate.js","./_generated/_framework/reified":"./_generated/_framework/reified.js","./_generated/_framework/util":"./_generated/_framework/util.js","./_generated/_framework/vector":"./_generated/_framework/vector.js","./_generated/suilend":"./_generated/suilend/index.js","./margin/margin/admin_cap":"./margin/margin/admin_cap.js","./margin/margin/market":"./margin/margin/market.js","./margin/margin/permissions":"./margin/margin/permissions.js","./margin/margin/position":"./margin/margin/position.js","./margin/margin/router":"./margin/margin/router.js","./margin/margin/version":"./margin/margin/version.js","./margin/utils":"./margin/utils/index.js","./_generated/suilend/cell/structs":"./_generated/suilend/cell/structs.js","./_generated/suilend/decimal/structs":"./_generated/suilend/decimal/structs.js","./_generated/suilend/lending-market/functions":"./_generated/suilend/lending-market/functions.js","./_generated/suilend/lending-market/structs":"./_generated/suilend/lending-market/structs.js","./_generated/suilend/lending-market-registry/functions":"./_generated/suilend/lending-market-registry/functions.js","./_generated/suilend/liquidity-mining/structs":"./_generated/suilend/liquidity-mining/structs.js","./_generated/suilend/obligation/structs":"./_generated/suilend/obligation/structs.js","./_generated/suilend/rate-limiter/functions":"./_generated/suilend/rate-limiter/functions.js","./_generated/suilend/rate-limiter/structs":"./_generated/suilend/rate-limiter/structs.js","./_generated/suilend/reserve/structs":"./_generated/suilend/reserve/structs.js","./_generated/suilend/reserve-config/functions":"./_generated/suilend/reserve-config/functions.js","./_generated/suilend/reserve-config/structs":"./_generated/suilend/reserve-config/structs.js","./_generated/_dependencies/source/0x1":"./_generated/_dependencies/source/0x1/index.js","./_generated/_dependencies/source/0x2":"./_generated/_dependencies/source/0x2/index.js","./_generated/_dependencies/source/0x8d97f1cd6ac663735be08d1d2b6d02a159e711586461306ce60a2b7a6a565a9e":"./_generated/_dependencies/source/0x8d97f1cd6ac663735be08d1d2b6d02a159e711586461306ce60a2b7a6a565a9e/index.js","./margin/margin/deps/std/type_name":"./margin/margin/deps/std/type_name.js","./margin/margin/deps/sui/vec_set":"./margin/margin/deps/sui/vec_set.js","./margin/margin/deps/suilend/lending_market":"./margin/margin/deps/suilend/lending_market.js","./_generated/_dependencies/source/0x1/ascii/structs":"./_generated/_dependencies/source/0x1/ascii/structs.js","./_generated/_dependencies/source/0x1/option/structs":"./_generated/_dependencies/source/0x1/option/structs.js","./_generated/_dependencies/source/0x1/type-name/structs":"./_generated/_dependencies/source/0x1/type-name/structs.js","./_generated/_dependencies/source/0x2/bag/structs":"./_generated/_dependencies/source/0x2/bag/structs.js","./_generated/_dependencies/source/0x2/balance/structs":"./_generated/_dependencies/source/0x2/balance/structs.js","./_generated/_dependencies/source/0x2/object/structs":"./_generated/_dependencies/source/0x2/object/structs.js","./_generated/_dependencies/source/0x2/object-table/structs":"./_generated/_dependencies/source/0x2/object-table/structs.js","./_generated/_dependencies/source/0x8d97f1cd6ac663735be08d1d2b6d02a159e711586461306ce60a2b7a6a565a9e/price-identifier/structs":"./_generated/_dependencies/source/0x8d97f1cd6ac663735be08d1d2b6d02a159e711586461306ce60a2b7a6a565a9e/price-identifier/structs.js"},"types":"./index.d.ts","scripts":{"build":"rm -rf ./dist && tsc && node ./fix-esm-imports.js","typecheck":"tsc --noEmit && tsc --noEmit -p tsconfig.test.json","test":"vitest run","lint:ci":"yarn run typecheck","prettier":"prettier --write src/ tests/","release":"yarn run build && node ./release.js && cd ./dist && npm publish --access public"},"repository":{"type":"git","url":"git+https://github.com/fireflyprotocol/lending-mono.git","directory":"ts/sdks/sdk"},"dependencies":{"@bluefin-exchange/bluefin7k-aggregator-sdk":"7.6.3","@cetusprotocol/aggregator-sdk":"1.7.2","@flowx-finance/sdk":"2.1.0","@pythnetwork/hermes-client":"3.1.0","@pythnetwork/pyth-sui-js":"4.0.0","@suilend/springsui-sdk":"4.1.0","bignumber.js":"11.1.5","bn.js":"5.2.5","crypto-js":"4.2.0","lodash":"4.18.1","p-limit":"7.3.2","uuid":"14.0.2"},"devDependencies":{"@mysten/bcs":"2.1.1","@mysten/sui":"2.29.0","@suilend/sui-core":"2.1.0","@tsconfig/recommended":"1.0.13","@types/bn.js":"5.2.0","@types/lodash":"4.17.25","@types/node":"26.5.1","fast-check":"4.10.0","prettier":"3.9.6","ts-node":"10.9.2","typescript":"6.0.3","vite":"8.3.0","vitest":"5.0.0"},"peerDependencies":{"@mysten/bcs":"^2.1.1","@mysten/sui":">=2.22.1 <3","@suilend/sui-core":"^2.0.1"},"type":"module"}
@@ -25,6 +25,7 @@ export declare const parseLendingMarket: (lendingMarket: LendingMarket<string>,
25
25
  isolated: boolean;
26
26
  openAttributedBorrowLimitUsd: number;
27
27
  closeAttributedBorrowLimitUsd: number;
28
+ additionalFields: import("../_generated/_dependencies/source/0x2/bag/structs").Bag;
28
29
  interestRate: {
29
30
  id: string;
30
31
  utilPercent: BigNumber;
@@ -45,6 +45,7 @@ export declare const parseObligation: (obligation: Obligation<string>, parsedRes
45
45
  isolated: boolean;
46
46
  openAttributedBorrowLimitUsd: number;
47
47
  closeAttributedBorrowLimitUsd: number;
48
+ additionalFields: import("../_generated/_dependencies/source/0x2/bag/structs").Bag;
48
49
  interestRate: {
49
50
  id: string;
50
51
  utilPercent: BigNumber;
@@ -162,6 +163,7 @@ export declare const parseObligation: (obligation: Obligation<string>, parsedRes
162
163
  isolated: boolean;
163
164
  openAttributedBorrowLimitUsd: number;
164
165
  closeAttributedBorrowLimitUsd: number;
166
+ additionalFields: import("../_generated/_dependencies/source/0x2/bag/structs").Bag;
165
167
  interestRate: {
166
168
  id: string;
167
169
  utilPercent: BigNumber;
@@ -25,6 +25,7 @@ export declare const parseReserve: (reserve: Reserve<string>, coinMetadataMap: R
25
25
  isolated: boolean;
26
26
  openAttributedBorrowLimitUsd: number;
27
27
  closeAttributedBorrowLimitUsd: number;
28
+ additionalFields: import("../_generated/_dependencies/source/0x2/bag/structs").Bag;
28
29
  interestRate: {
29
30
  id: string;
30
31
  utilPercent: BigNumber;
@@ -147,6 +148,7 @@ export declare const parseReserveConfig: (reserve: Reserve<string>) => {
147
148
  isolated: boolean;
148
149
  openAttributedBorrowLimitUsd: number;
149
150
  closeAttributedBorrowLimitUsd: number;
151
+ additionalFields: import("../_generated/_dependencies/source/0x2/bag/structs").Bag;
150
152
  interestRate: {
151
153
  id: string;
152
154
  utilPercent: BigNumber;
@@ -122,7 +122,7 @@ export const parseReserveConfig = (reserve) => {
122
122
  const isolated = config.isolated;
123
123
  const openAttributedBorrowLimitUsd = Number(config.openAttributedBorrowLimitUsd.toString());
124
124
  const closeAttributedBorrowLimitUsd = Number(config.closeAttributedBorrowLimitUsd.toString());
125
- // additionalFields
125
+ const additionalFields = config.additionalFields;
126
126
  const interestRate = config.interestRateUtils.map((util, index) => ({
127
127
  id: uuidv4(),
128
128
  utilPercent: new BigNumber(util.toString()),
@@ -146,10 +146,12 @@ export const parseReserveConfig = (reserve) => {
146
146
  isolated,
147
147
  openAttributedBorrowLimitUsd,
148
148
  closeAttributedBorrowLimitUsd,
149
- // additionalFields,
149
+ additionalFields,
150
150
  interestRate,
151
151
  };
152
152
  };
153
+ // additionalFields is a Bag; read the confidence multiplier via
154
+ // SuilendClient.getReserveConfidenceMultiplierBps(suiGrpcClient, additionalFields.id).
153
155
  export const parsePoolRewardManager = (poolRewardManager, coinMetadataMap) => {
154
156
  const $typeName = poolRewardManager.$typeName;
155
157
  const id = poolRewardManager.id;
package/strategies.d.ts CHANGED
@@ -73,6 +73,7 @@ export declare const getStrategySuiReserve: (reserveMap: Record<string, ParsedRe
73
73
  isolated: boolean;
74
74
  openAttributedBorrowLimitUsd: number;
75
75
  closeAttributedBorrowLimitUsd: number;
76
+ additionalFields: import("./_generated/_dependencies/source/0x2/bag/structs").Bag;
76
77
  interestRate: {
77
78
  id: string;
78
79
  utilPercent: BigNumber;
@@ -202,6 +203,7 @@ export declare const getStrategyDefaultCurrencyReserve: (reserveMap: Record<stri
202
203
  isolated: boolean;
203
204
  openAttributedBorrowLimitUsd: number;
204
205
  closeAttributedBorrowLimitUsd: number;
206
+ additionalFields: import("./_generated/_dependencies/source/0x2/bag/structs").Bag;
205
207
  interestRate: {
206
208
  id: string;
207
209
  utilPercent: BigNumber;
@@ -297,7 +299,8 @@ export declare const getStrategyBorrowedAmount: (reserveMap: Record<string, Pars
297
299
  export declare const getStrategyTvlAmount: (reserveMap: Record<string, ParsedReserve>, lstMap: StrategyLstMap, strategyType: StrategyType, obligation?: ParsedObligation) => BigNumber;
298
300
  export declare const getStrategyExposure: (reserveMap: Record<string, ParsedReserve>, lstMap: StrategyLstMap, strategyType: StrategyType, obligation?: ParsedObligation) => BigNumber;
299
301
  export declare const getStrategyStepMaxBorrowedAmount: (reserveMap: Record<string, ParsedReserve>, lstMap: StrategyLstMap, strategyType: StrategyType, deposits: StrategyDeposit[], borrowedAmount: BigNumber) => BigNumber;
300
- export declare const getStrategyStepMaxWithdrawnAmount: (reserveMap: Record<string, ParsedReserve>, lstMap: StrategyLstMap, strategyType: StrategyType, deposits: StrategyDeposit[], borrowedAmount: BigNumber, withdrawCoinType: string) => BigNumber;
302
+ export declare const getStrategyStepMaxWithdrawnAmount: (reserveMap: Record<string, ParsedReserve>, lstMap: StrategyLstMap, strategyType: StrategyType, deposits: StrategyDeposit[], borrowedAmount: BigNumber, withdrawCoinType: string, headroomBuffer?: number) => BigNumber;
303
+ export declare const getStrategyRebalanceDepositAmount: (reserveMap: Record<string, ParsedReserve>, lstMap: StrategyLstMap, strategyType: StrategyType, deposits: StrategyDeposit[], borrowedAmount: BigNumber) => BigNumber;
301
304
  export declare const strategySimulateLoopToExposure: (reserveMap: Record<string, ParsedReserve>, lstMap: StrategyLstMap, strategyType: StrategyType, _deposits: StrategyDeposit[], _borrowedAmount: BigNumber, _targetBorrowedAmount: BigNumber | undefined, _targetExposure: BigNumber | undefined) => {
302
305
  deposits: StrategyDeposit[];
303
306
  borrowedAmount: BigNumber;
package/strategies.js CHANGED
@@ -10,7 +10,7 @@ import { getNetAprPercent } from "./lib/index.js";
10
10
  import { getRewardsMap } from "./lib/liquidityMining.js";
11
11
  import { STRATEGY_TYPE_INFO_MAP, StrategyType, strategyBorrow, strategyClaimRewardsAndMergeCoins, strategyDeposit, strategyWithdraw, } from "./lib/strategyOwnerCap.js";
12
12
  import { MMT_CONTRACT_PACKAGE_ID, MMT_VERSION_OBJECT_ID } from "./mmt.js";
13
- import { getWeightedBorrowsUsd } from "./utils/index.js";
13
+ import { bisectionMethod, getWeightedBorrowsUsd } from "./utils/index.js";
14
14
  export const STRATEGY_E = 10 ** -7;
15
15
  export const STRATEGY_FLASH_REPAY_MAX_RESIDUAL_USD = 0.1;
16
16
  /**
@@ -591,7 +591,7 @@ export const getStrategyStepMaxWithdrawnAmount = (
591
591
  // AppContext
592
592
  reserveMap,
593
593
  // Strategy
594
- lstMap, strategyType, deposits, borrowedAmount, withdrawCoinType) => {
594
+ lstMap, strategyType, deposits, borrowedAmount, withdrawCoinType, headroomBuffer = 1) => {
595
595
  const depositReserves = getStrategyDepositReserves(reserveMap, strategyType);
596
596
  const borrowReserve = getStrategyBorrowReserve(reserveMap, strategyType);
597
597
  const defaultCurrencyReserve = getStrategyDefaultCurrencyReserve(reserveMap, strategyType);
@@ -608,10 +608,33 @@ lstMap, strategyType, deposits, borrowedAmount, withdrawCoinType) => {
608
608
  .minus(obligation.maxPriceWeightedBorrowsUsd)
609
609
  .div(withdrawReserve.minPrice)
610
610
  .div(withdrawReserve.config.openLtvPct / 100)
611
+ .decimalPlaces(withdrawReserve.token.decimals, BigNumber.ROUND_DOWN)
612
+ .times(headroomBuffer)
611
613
  : MAX_U64, // Infinity
612
614
  deposits.find((deposit) => deposit.coinType === withdrawReserve.coinType)
613
615
  ?.depositedAmount ?? new BigNumber(0)).decimalPlaces(withdrawReserve.token.decimals, BigNumber.ROUND_DOWN);
614
616
  };
617
+ export const getStrategyRebalanceDepositAmount = (
618
+ // AppContext
619
+ reserveMap,
620
+ // Strategy
621
+ lstMap, strategyType, deposits, borrowedAmount) => {
622
+ const depositReserves = getStrategyDepositReserves(reserveMap, strategyType);
623
+ const depositReserve = (depositReserves.base ?? depositReserves.lst); // Must have LST if no base
624
+ const depositedAmount = deposits.find((d) => d.coinType === depositReserve.coinType)
625
+ ?.depositedAmount ?? new BigNumber(0);
626
+ if (depositedAmount.lte(0))
627
+ return new BigNumber(0);
628
+ const simulateWith = (newDepositedAmount) => getStrategySimulatedObligation(reserveMap, lstMap, strategyType, addOrInsertStrategyDeposit(deposits, {
629
+ coinType: depositReserve.coinType,
630
+ depositedAmount: newDepositedAmount.minus(depositedAmount),
631
+ }), borrowedAmount);
632
+ const withinMaxExposure = bisectionMethod(depositedAmount, depositedAmount.times(2), (n) => getStrategyExposure(reserveMap, lstMap, strategyType, simulateWith(n)).lte(STRATEGY_TYPE_EXPOSURE_MAP[strategyType].max));
633
+ const fullHealth = bisectionMethod(depositedAmount, depositedAmount.times(2), (n) => getStrategyHealthPercent(reserveMap, lstMap, strategyType, simulateWith(n)).eq(100));
634
+ return BigNumber.max(withinMaxExposure, fullHealth)
635
+ .minus(depositedAmount)
636
+ .decimalPlaces(depositReserve.token.decimals, BigNumber.ROUND_UP);
637
+ };
615
638
  // Simulate
616
639
  export const strategySimulateLoopToExposure = (
617
640
  // AppContext
@@ -1236,7 +1259,7 @@ transaction, dryRunTransaction) => {
1236
1259
  _targetExposure: _targetExposure?.toFixed(20),
1237
1260
  }, null, 2));
1238
1261
  const depositReserve = (depositReserves.base ?? depositReserves.lst); // Must have LST if no base
1239
- const loopingDepositReserve = (depositReserves.lst ?? depositReserves.base); // Must have base if no LST
1262
+ let loopingDepositReserve = (depositReserves.lst ?? depositReserves.base); // Must have base if no LST
1240
1263
  //
1241
1264
  let deposits = cloneDeep(_deposits);
1242
1265
  let borrowedAmount = _borrowedAmount;
@@ -1426,7 +1449,9 @@ transaction, dryRunTransaction) => {
1426
1449
  fullRepaymentAmount: fullRepaymentAmount.toFixed(20),
1427
1450
  }));
1428
1451
  // 1) MAX withdraw LST
1429
- if (depositReserves.lst !== undefined) {
1452
+ if (depositReserves.lst !== undefined &&
1453
+ (deposits.find((d) => d.coinType === depositReserves.lst.coinType)
1454
+ ?.depositedAmount ?? new BigNumber(0)).gt(0)) {
1430
1455
  // 1.1) MAX withdraw
1431
1456
  const [withdrawnMaxLstCoin] = strategyWithdraw(strategyType, depositReserves.lst.coinType, strategyOwnerCapId, suilendClient.findReserveArrayIndex(depositReserves.lst.coinType), BigInt(MAX_U64.toString()), transaction);
1432
1457
  // 1.2) Update state
@@ -1585,9 +1610,10 @@ transaction, dryRunTransaction) => {
1585
1610
  if (pendingBorrowedAmount.lt(0))
1586
1611
  break; // Fully repaid already
1587
1612
  if (depositReserve.coinType === depositReserves.base?.coinType) {
1588
- const lstDeposit = deposits.find((d) => d.coinType === depositReserves.lst.coinType);
1613
+ const lstDepositedAmount = deposits.find((d) => d.coinType === depositReserves.lst.coinType)
1614
+ ?.depositedAmount ?? new BigNumber(0);
1589
1615
  // Ran out of LST
1590
- if (lstDeposit.depositedAmount.lte(STRATEGY_E)) {
1616
+ if (lstDepositedAmount.lte(STRATEGY_E)) {
1591
1617
  // 1. MAX withdraws LST (transferred to user as SUI)
1592
1618
  // 2. Withdraws base to cover borrows
1593
1619
  // - Leftover transferred to user as borrow coinType, e.g. SUI or suiWBTC
@@ -1643,12 +1669,17 @@ transaction, dryRunTransaction) => {
1643
1669
  else {
1644
1670
  if (pendingBorrowedAmount.lte(STRATEGY_E))
1645
1671
  break;
1672
+ // The rest of the debt is backed by base, so keep unwinding from it.
1673
+ if (depositReserves.base !== undefined &&
1674
+ (deposits.find((d) => d.coinType === depositReserves.lst.coinType)
1675
+ ?.depositedAmount ?? new BigNumber(0)).lte(STRATEGY_E)) {
1676
+ loopingDepositReserve = depositReserves.base;
1677
+ continue;
1678
+ }
1646
1679
  }
1647
1680
  // 1) Withdraw LST
1648
1681
  // 1.1) Max
1649
- const stepMaxWithdrawnAmount = getStrategyStepMaxWithdrawnAmount(reserveMap, lstMap, strategyType, deposits, borrowedAmount, loopingDepositReserve.coinType)
1650
- .times(0.9) // 10% buffer
1651
- .decimalPlaces(loopingDepositReserve.token.decimals, BigNumber.ROUND_DOWN);
1682
+ const stepMaxWithdrawnAmount = getStrategyStepMaxWithdrawnAmount(reserveMap, lstMap, strategyType, deposits, borrowedAmount, loopingDepositReserve.coinType, 0.9);
1652
1683
  const stepMaxRepaidAmount = new BigNumber(new BigNumber(stepMaxWithdrawnAmount.times(lst?.lstToSuiExchangeRate ?? 1)).minus(getStrategyLstRedeemFee(lstMap, loopingDepositReserve.coinType, stepMaxWithdrawnAmount))).decimalPlaces(borrowReserve.token.decimals, BigNumber.ROUND_DOWN);
1653
1684
  console.log(`[unloopStrategyToExposure] ${i} withdraw_lst.max |`, JSON.stringify({
1654
1685
  stepMaxWithdrawnAmount: stepMaxWithdrawnAmount.toFixed(20),
@@ -1785,9 +1816,7 @@ transaction, dryRunTransaction) => {
1785
1816
  }
1786
1817
  // 1) Withdraw base
1787
1818
  // 1.1) Max
1788
- const stepMaxWithdrawnAmount = getStrategyStepMaxWithdrawnAmount(reserveMap, lstMap, strategyType, deposits, borrowedAmount, loopingDepositReserve.coinType)
1789
- .times(0.9) // 10% buffer
1790
- .decimalPlaces(loopingDepositReserve.token.decimals, BigNumber.ROUND_DOWN);
1819
+ const stepMaxWithdrawnAmount = getStrategyStepMaxWithdrawnAmount(reserveMap, lstMap, strategyType, deposits, borrowedAmount, loopingDepositReserve.coinType, 0.9);
1791
1820
  const stepMaxRepaidAmount = new BigNumber(stepMaxWithdrawnAmount.times(baseToBorrowExchangeRate)).decimalPlaces(borrowReserve.token.decimals, BigNumber.ROUND_DOWN);
1792
1821
  console.log(`[unloopStrategyToExposure] ${i} withdraw_base.max |`, JSON.stringify({
1793
1822
  stepMaxWithdrawnAmount: stepMaxWithdrawnAmount.toFixed(20),
@@ -2351,6 +2380,17 @@ lstMap, strategyType, suiGrpcClient, suilendClient, cetusSdk, cetusPartnerId, _a
2351
2380
  let borrowedAmount = _borrowedAmount;
2352
2381
  // 1) Unloop to 1x (base+LST: no LST and no borrows, LST: no borrows)
2353
2382
  if (borrowedAmount.gt(0)) {
2383
+ // 1.0) Above the borrow limit no stepwise withdraw can land, so rebalance first
2384
+ if (!isStrategyWithinBorrowLimit(reserveMap, lstMap, strategyType, deposits, borrowedAmount)) {
2385
+ try {
2386
+ ({ deposits, borrowedAmount, transaction } =
2387
+ await strategyRebalanceWithinBorrowLimitTx(reserveMap, lstMap, strategyType, suiGrpcClient, suilendClient, cetusSdk, cetusPartnerId, _address, strategyOwnerCapId, obligationId, deposits, borrowedAmount, Transaction.from(transaction), dryRunTransaction));
2388
+ }
2389
+ catch (err) {
2390
+ console.error(err);
2391
+ throw new Error("Failed to repay borrows before withdrawing");
2392
+ }
2393
+ }
2354
2394
  // 1.1) Unloop
2355
2395
  const { deposits: newDeposits, borrowedAmount: newBorrowedAmount, transaction: newTransaction, } = await strategyUnloopToExposureTx(reserveMap, lstMap, strategyType, suiGrpcClient, suilendClient, cetusSdk, cetusPartnerId, _address, strategyOwnerCapId, obligationId, deposits, borrowedAmount, undefined, // Don't pass targetBorrowedAmount
2356
2396
  new BigNumber(1), // Pass targetExposure
@@ -2373,6 +2413,30 @@ lstMap, strategyType, suiGrpcClient, suilendClient, cetusSdk, cetusPartnerId, _a
2373
2413
  // rather than build a transaction that is certain to abort on the health check —
2374
2414
  // the caller can surface this instead of asking the user to sign it.
2375
2415
  let flashLoan;
2416
+ let repaidByAdjustRepay = false;
2417
+ if (borrowedAmount.gt(STRATEGY_E) &&
2418
+ depositReserves.base !== undefined &&
2419
+ depositReserves.lst !== undefined) {
2420
+ const baseFlashLoanAmount = borrowedAmount
2421
+ .times(1.01)
2422
+ .decimalPlaces(borrowReserve.token.decimals, BigNumber.ROUND_UP)
2423
+ .times(borrowReserve.price)
2424
+ .div(depositReserves.base.price)
2425
+ .times(1.03)
2426
+ .decimalPlaces(depositReserves.base.token.decimals, BigNumber.ROUND_UP);
2427
+ console.log(`[strategyMaxWithdraw] adjust_repay |`, JSON.stringify({
2428
+ borrowedAmount: borrowedAmount.toFixed(20),
2429
+ baseFlashLoanAmount: baseFlashLoanAmount.toFixed(20),
2430
+ }));
2431
+ try {
2432
+ ({ deposits, borrowedAmount, transaction } = await strategyAdjustRepayTx(reserveMap, lstMap, strategyType, suiGrpcClient, suilendClient, cetusSdk, cetusPartnerId, _address, strategyOwnerCapId, obligationId, deposits, borrowedAmount, baseFlashLoanAmount, Transaction.from(transaction), dryRunTransaction));
2433
+ }
2434
+ catch (err) {
2435
+ console.error(err);
2436
+ throw new Error("Failed to repay borrows before withdrawing");
2437
+ }
2438
+ repaidByAdjustRepay = true;
2439
+ }
2376
2440
  if (borrowedAmount.gt(STRATEGY_E)) {
2377
2441
  const flashLoanObj = STRATEGY_TYPE_FLASH_LOAN_OBJ_MAP[strategyType];
2378
2442
  if (flashLoanObj.provider !== StrategyFlashLoanProvider.MMT ||
@@ -2525,7 +2589,7 @@ lstMap, strategyType, suiGrpcClient, suilendClient, cetusSdk, cetusPartnerId, _a
2525
2589
  transaction = transactionBeforeClaim;
2526
2590
  }
2527
2591
  }
2528
- if (flashLoan !== undefined || borrowedAmount.gt(0)) {
2592
+ if (flashLoan !== undefined || repaidByAdjustRepay || borrowedAmount.gt(0)) {
2529
2593
  try {
2530
2594
  await dryRunTransaction(transaction);
2531
2595
  }
@@ -2971,6 +3035,8 @@ lstMap, strategyType, suiGrpcClient, suilendClient, cetusSdk, cetusPartnerId, _a
2971
3035
  }
2972
3036
  return { deposits, borrowedAmount, transaction };
2973
3037
  };
3038
+ const isStrategyWithinBorrowLimit = (reserveMap, lstMap, strategyType, deposits, borrowedAmount) => getStrategyHealthPercent(reserveMap, lstMap, strategyType, getStrategySimulatedObligation(reserveMap, lstMap, strategyType, deposits, borrowedAmount)).eq(100);
3039
+ const strategyRebalanceWithinBorrowLimitTx = (reserveMap, lstMap, strategyType, suiGrpcClient, suilendClient, cetusSdk, cetusPartnerId, _address, strategyOwnerCapId, obligationId, deposits, borrowedAmount, transaction, dryRunTransaction) => strategyDepositAdjustWithdrawTx(reserveMap, lstMap, strategyType, suiGrpcClient, suilendClient, cetusSdk, cetusPartnerId, _address, strategyOwnerCapId, obligationId, deposits, borrowedAmount, getStrategyRebalanceDepositAmount(reserveMap, lstMap, strategyType, deposits, borrowedAmount), transaction, dryRunTransaction);
2974
3040
  export const strategyAdjustTx = async (
2975
3041
  // AppContext
2976
3042
  reserveMap,
@@ -2983,7 +3049,7 @@ lstMap, strategyType, suiGrpcClient, suilendClient, cetusSdk, cetusPartnerId, _a
2983
3049
  const depositReserves = getStrategyDepositReserves(reserveMap, strategyType);
2984
3050
  const borrowReserve = getStrategyBorrowReserve(reserveMap, strategyType);
2985
3051
  const defaultCurrencyReserve = getStrategyDefaultCurrencyReserve(reserveMap, strategyType);
2986
- const exposure = getStrategyExposure(reserveMap, lstMap, strategyType, obligation);
3052
+ let exposure = getStrategyExposure(reserveMap, lstMap, strategyType, obligation);
2987
3053
  console.log(`[strategyAdjust] args |`, JSON.stringify({
2988
3054
  _address,
2989
3055
  strategyOwnerCapId,
@@ -2996,17 +3062,31 @@ lstMap, strategyType, suiGrpcClient, suilendClient, cetusSdk, cetusPartnerId, _a
2996
3062
  targetExposure: targetExposure.toFixed(20),
2997
3063
  }, null, 2));
2998
3064
  //
2999
- const deposits = cloneDeep(_deposits);
3000
- const borrowedAmount = _borrowedAmount;
3001
- // 1) Loop or unloop to target exposure
3065
+ let deposits = cloneDeep(_deposits);
3066
+ let borrowedAmount = _borrowedAmount;
3067
+ // 1) Above the borrow limit no stepwise withdraw can land, so rebalance first
3068
+ const rebalanced = borrowedAmount.gt(0) && !isStrategyWithinBorrowLimit(reserveMap, lstMap, strategyType, deposits, borrowedAmount);
3069
+ if (rebalanced) {
3070
+ ({ deposits, borrowedAmount, transaction } =
3071
+ await strategyRebalanceWithinBorrowLimitTx(reserveMap, lstMap, strategyType, suiGrpcClient, suilendClient, cetusSdk, cetusPartnerId, _address, strategyOwnerCapId, obligation.id, deposits, borrowedAmount, transaction, dryRunTransaction));
3072
+ exposure = getStrategyExposure(reserveMap, lstMap, strategyType, getStrategySimulatedObligation(reserveMap, lstMap, strategyType, deposits, borrowedAmount));
3073
+ }
3074
+ // 2) Loop or unloop to target exposure
3075
+ let result;
3002
3076
  if (targetExposure.gt(exposure))
3003
- return strategyLoopToExposureTx(reserveMap, lstMap, strategyType, suiGrpcClient, suilendClient, cetusSdk, cetusPartnerId, _address, strategyOwnerCapId, obligation.id, deposits, borrowedAmount, undefined, // Don't pass targetBorrowedAmount
3077
+ result = await strategyLoopToExposureTx(reserveMap, lstMap, strategyType, suiGrpcClient, suilendClient, cetusSdk, cetusPartnerId, _address, strategyOwnerCapId, obligation.id, deposits, borrowedAmount, undefined, // Don't pass targetBorrowedAmount
3004
3078
  targetExposure, // Pass targetExposure
3005
3079
  transaction);
3006
3080
  else if (targetExposure.lt(exposure))
3007
- return strategyUnloopToExposureTx(reserveMap, lstMap, strategyType, suiGrpcClient, suilendClient, cetusSdk, cetusPartnerId, _address, strategyOwnerCapId, obligation.id, deposits, borrowedAmount, undefined, // Don't pass targetBorrowedAmount
3081
+ result = await strategyUnloopToExposureTx(reserveMap, lstMap, strategyType, suiGrpcClient, suilendClient, cetusSdk, cetusPartnerId, _address, strategyOwnerCapId, obligation.id, deposits, borrowedAmount, undefined, // Don't pass targetBorrowedAmount
3008
3082
  targetExposure, // Pass targetExposure
3009
3083
  transaction, dryRunTransaction);
3010
3084
  else
3011
3085
  return { deposits, borrowedAmount, transaction };
3086
+ if (result.borrowedAmount.eq(borrowedAmount)) {
3087
+ if (rebalanced)
3088
+ return { deposits, borrowedAmount, transaction };
3089
+ throw new Error("Failed to adjust leverage");
3090
+ }
3091
+ return result;
3012
3092
  };