@suilend/sdk 12.2.0-next.g344182f → 12.3.0-next.g22670e4

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.
package/client.d.ts CHANGED
@@ -8,6 +8,14 @@ import { NewConfigArgs as CreateRateLimiterConfigArgs } from "./_generated/suile
8
8
  import { CreateReserveConfigArgs } from "./_generated/suilend/reserve-config/functions";
9
9
  import { type PriceUpdateDataSource } from "./lib/pyth";
10
10
  import { Side } from "./lib/types";
11
+ /**
12
+ * Signed Pyth update data the caller already fetched for `priceIds`, posted
13
+ * unconditionally instead of the on-chain staleness check deciding.
14
+ */
15
+ export interface PriceUpdate {
16
+ priceIds: string[];
17
+ updateData: Buffer[];
18
+ }
11
19
  export declare function needsPriceRefresh(stalenessSeconds: number): boolean;
12
20
  export declare const ADMIN_ADDRESS: string;
13
21
  export declare const LENDING_MARKET_REGISTRY_ID: string;
@@ -99,7 +107,7 @@ export declare class SuilendClient {
99
107
  updateRateLimiterConfig(lendingMarketOwnerCapId: string, transaction: Transaction, newRateLimiterConfigArgs: CreateRateLimiterConfigArgs): import("@mysten/sui/transactions").TransactionResult;
100
108
  changeReservePriceFeed(lendingMarketOwnerCapId: string, coinType: string, pythPriceId: string, transaction: Transaction): Promise<import("@mysten/sui/transactions").TransactionResult>;
101
109
  createObligation(transaction: Transaction): import("@mysten/sui/transactions").TransactionResult;
102
- refreshAll(transaction: Transaction, obligation?: Obligation<string>, coinTypes?: string[]): Promise<void>;
110
+ refreshAll(transaction: Transaction, obligation?: Obligation<string>, coinTypes?: string[], priceUpdate?: PriceUpdate): Promise<void>;
103
111
  refreshReservePrices(transaction: Transaction, priceInfoObjectId: string, reserveArrayIndex: bigint): Promise<void>;
104
112
  deposit(sendCoin: TransactionObjectInput, coinType: string, obligationOwnerCap: TransactionObjectInput, transaction: Transaction): void;
105
113
  depositIntoObligation(ownerId: string, coinType: string, value: string, transaction: Transaction, obligationOwnerCap: TransactionObjectInput): Promise<void>;
@@ -111,8 +119,8 @@ export declare class SuilendClient {
111
119
  borrowAndSendToUser(ownerId: string, obligationOwnerCap: TransactionObjectInput, obligationId: string, coinType: string, value: string, transaction: Transaction): Promise<void>;
112
120
  repay(obligationId: string, coinType: string, coin: TransactionObjectInput, transaction: Transaction): import("@mysten/sui/transactions").TransactionResult;
113
121
  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>;
122
+ liquidateAndRedeem(transaction: Transaction, obligation: Obligation<string>, repayCoinType: string, withdrawCoinType: string, repayCoinId: TransactionObjectInput, priceUpdate?: PriceUpdate): Promise<import("@mysten/sui/transactions").TransactionResult>;
123
+ liquidate(transaction: Transaction, obligation: Obligation<string>, repayCoinType: string, withdrawCoinType: string, repayCoinId: TransactionObjectInput, priceUpdate?: PriceUpdate): Promise<import("@mysten/sui/transactions").TransactionResult>;
116
124
  migrate(transaction: Transaction, lendingMarketOwnerCapId: string): import("@mysten/sui/transactions").TransactionResult;
117
125
  claimFees(transaction: Transaction, coinType: string): import("@mysten/sui/transactions").TransactionResult;
118
126
  setFeeReceiversAndWeights(transaction: Transaction, lendingMarketOwnerCapId: string, receivers: string[], weights: bigint[]): import("@mysten/sui/transactions").TransactionResult;
package/client.js CHANGED
@@ -28,6 +28,7 @@ const isSui = (coinType) => normalizeStructTag(coinType) === NORMALIZED_SUI_COIN
28
28
  // refresh would come from a package the contract does not accept.
29
29
  const WORMHOLE_STATE_ID = "0xdbca52b9fb4f712e25f61f974586d93ac541bcf8389564f0323bb07215168b5c";
30
30
  const PYTH_STATE_ID = "0x03719fae774ddab3cfcaa53bbc046f0cbe21410019b6280811bf3f9f4b05839d";
31
+ const normalizePriceIdentifier = (id) => id.toLowerCase().replace(/^0x/, "");
31
32
  // Mirrors suilend::oracles's MAX_FORWARD_SECONDS (move/contracts/suilend/sources/oracles.move);
32
33
  // `refresh_reserve_price` now rejects a forward-dated price the same as a stale one.
33
34
  const MAX_FORWARD_SECONDS = 60;
@@ -475,7 +476,7 @@ export class SuilendClient {
475
476
  typeArguments: this.lendingMarket.$typeArgs,
476
477
  });
477
478
  }
478
- async refreshAll(transaction, obligation, coinTypes) {
479
+ async refreshAll(transaction, obligation, coinTypes, priceUpdate) {
479
480
  const reserveArrayIndexToPriceId = new Map();
480
481
  if (obligation) {
481
482
  obligation.deposits.forEach((deposit) => {
@@ -505,8 +506,14 @@ export class SuilendClient {
505
506
  const priceInfoObjectId = await this.pythClient.getPriceFeedObjectId(priceIdentifiers[i]);
506
507
  priceInfoObjectIds[i] = priceInfoObjectId;
507
508
  })()));
508
- const stalePriceIdentifiers = [];
509
- await Promise.all(Object.entries(priceInfoObjectIds).map(([i, priceInfoObjectId]) => (async () => {
509
+ const suppliedPriceIdentifiers = new Set(priceUpdate?.priceIds.map(normalizePriceIdentifier));
510
+ if (priceUpdate?.priceIds.length) {
511
+ await this.pythClient.updatePriceFeeds(transaction, priceUpdate.updateData, priceUpdate.priceIds);
512
+ }
513
+ const stalePriceIdentifiers = new Set();
514
+ await Promise.all(Object.entries(priceInfoObjectIds)
515
+ .filter(([i]) => !suppliedPriceIdentifiers.has(normalizePriceIdentifier(priceIdentifiers[+i])))
516
+ .map(([i, priceInfoObjectId]) => (async () => {
510
517
  // Read the field directly: the generated parser's type check is
511
518
  // bound to one Pyth package address and would throw post-cutover.
512
519
  const { object } = await this.suiGrpcClient.getObject({
@@ -516,15 +523,16 @@ export class SuilendClient {
516
523
  const publishTime = priceInfoPublishTime(object.json, priceInfoObjectId);
517
524
  const stalenessSeconds = Date.now() / 1000 - Number(publishTime);
518
525
  if (needsPriceRefresh(stalenessSeconds)) {
519
- stalePriceIdentifiers.push(priceIdentifiers[+i]);
526
+ stalePriceIdentifiers.add(priceIdentifiers[+i]);
520
527
  }
521
528
  })()));
522
- if (stalePriceIdentifiers.length > 0) {
523
- const stalePriceUpdateData = await this.pythConnection.getPriceFeedsUpdateData(stalePriceIdentifiers);
524
- await this.pythClient.updatePriceFeeds(transaction, stalePriceUpdateData, stalePriceIdentifiers);
529
+ if (stalePriceIdentifiers.size > 0) {
530
+ const staleIds = [...stalePriceIdentifiers];
531
+ const stalePriceUpdateData = await this.pythConnection.getPriceFeedsUpdateData(staleIds);
532
+ await this.pythClient.updatePriceFeeds(transaction, stalePriceUpdateData, staleIds);
525
533
  }
526
534
  for (let i = 0; i < reserveArrayIndexes.length; i++) {
527
- this.refreshReservePrices(transaction, priceInfoObjectIds[i], reserveArrayIndexes[i]);
535
+ await this.refreshReservePrices(transaction, priceInfoObjectIds[i], reserveArrayIndexes[i]);
528
536
  }
529
537
  }
530
538
  async refreshReservePrices(transaction, priceInfoObjectId, reserveArrayIndex) {
@@ -670,8 +678,8 @@ export class SuilendClient {
670
678
  transaction.transferObjects([sendCoin], transaction.pure.address(ownerId));
671
679
  return result;
672
680
  }
673
- async liquidateAndRedeem(transaction, obligation, repayCoinType, withdrawCoinType, repayCoinId) {
674
- const [ctokens, exemption] = await this.liquidate(transaction, obligation, repayCoinType, withdrawCoinType, repayCoinId);
681
+ async liquidateAndRedeem(transaction, obligation, repayCoinType, withdrawCoinType, repayCoinId, priceUpdate) {
682
+ const [ctokens, exemption] = await this.liquidate(transaction, obligation, repayCoinType, withdrawCoinType, repayCoinId, priceUpdate);
675
683
  const [optionalExemption] = transaction.moveCall({
676
684
  target: `0x1::option::some`,
677
685
  typeArguments: [
@@ -681,8 +689,8 @@ export class SuilendClient {
681
689
  });
682
690
  return this.redeem(ctokens, withdrawCoinType, optionalExemption, transaction);
683
691
  }
684
- async liquidate(transaction, obligation, repayCoinType, withdrawCoinType, repayCoinId) {
685
- await this.refreshAll(transaction, obligation);
692
+ async liquidate(transaction, obligation, repayCoinType, withdrawCoinType, repayCoinId, priceUpdate) {
693
+ await this.refreshAll(transaction, obligation, undefined, priceUpdate);
686
694
  return liquidate(transaction, [this.lendingMarket.$typeArgs[0], repayCoinType, withdrawCoinType], {
687
695
  lendingMarket: transaction.object(this.lendingMarket.id),
688
696
  obligationId: obligation.id,
package/package.json CHANGED
@@ -1 +1 @@
1
- {"name":"@suilend/sdk","version":"12.2.0-next.g344182f","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.3.0-next.g22670e4","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"}
package/strategies.d.ts CHANGED
@@ -27,6 +27,7 @@ export declare const STRATEGY_FLASH_REPAY_MAX_RESIDUAL_USD = 0.1;
27
27
  * tolerance stays available on the unwind path, and entry is what gets blocked.
28
28
  */
29
29
  export declare const STRATEGY_MIN_POSITION_USD = 10;
30
+ export declare const USD_DISPLAY_DECIMAL_PLACES = 2;
30
31
  export declare const LST_DECIMALS = 9;
31
32
  export type StrategyDeposit = {
32
33
  coinType: string;
package/strategies.js CHANGED
@@ -65,6 +65,7 @@ const SWAP_MAX_QUOTE_EXCESS_PERCENT = 50;
65
65
  * tolerance stays available on the unwind path, and entry is what gets blocked.
66
66
  */
67
67
  export const STRATEGY_MIN_POSITION_USD = 10;
68
+ export const USD_DISPLAY_DECIMAL_PLACES = 2;
68
69
  const resolveSwapSlippage = (amountIn, quotedAmountOut, sellReserve, buyReserve) => {
69
70
  if (quotedAmountOut.lte(0))
70
71
  throw new Error("No swap quote found");
@@ -2118,8 +2119,11 @@ lstMap, strategyType, suiGrpcClient, suilendClient, cetusSdk, cetusPartnerId, _a
2118
2119
  // conversion — where a one-cent allowance is a fraction of a percent, nowhere
2119
2120
  // near the regime this refuses. An existing position below the line can still
2120
2121
  // be adjusted and unwound either way.
2121
- const resultingTvlUsd = getStrategyTvlAmount(reserveMap, lstMap, strategyType, getStrategySimulatedObligation(reserveMap, lstMap, strategyType, addOrInsertStrategyDeposit(cloneDeep(_deposits), deposit), _borrowedAmount)).times(defaultCurrencyReserve.minPrice);
2122
- if (targetExposure.gt(1) && resultingTvlUsd.lt(STRATEGY_MIN_POSITION_USD))
2122
+ const resultingTvlUsd = getStrategyTvlAmount(reserveMap, lstMap, strategyType, getStrategySimulatedObligation(reserveMap, lstMap, strategyType, addOrInsertStrategyDeposit(cloneDeep(_deposits), deposit), _borrowedAmount)).times(defaultCurrencyReserve.price);
2123
+ if (targetExposure.gt(1) &&
2124
+ resultingTvlUsd
2125
+ .decimalPlaces(USD_DISPLAY_DECIMAL_PLACES, BigNumber.ROUND_HALF_UP)
2126
+ .lt(STRATEGY_MIN_POSITION_USD))
2123
2127
  throw new Error(`Position must be at least $${STRATEGY_MIN_POSITION_USD} to open`);
2124
2128
  let deposits = cloneDeep(_deposits);
2125
2129
  let borrowedAmount = _borrowedAmount;
@@ -197,6 +197,42 @@ export type ObligationHealth = {
197
197
  * All math stays in BigNumber end-to-end — never construct a `Decimal` from
198
198
  * a stored quantity; `numberToDecimal`/`stringToDecimal` round through a JS
199
199
  * `number` (`Math.round`) and are lossy at WAD scale. `decimalToBigNumber` is
200
- * only ever called on a genuine on-chain `Reserve` field below.
200
+ * only ever called on a genuine on-chain `Reserve` field, in
201
+ * `buildReserveHealthTable`.
202
+ *
203
+ * Per-position operation order matches `getCTokenMarketValue` and
204
+ * `compoundBorrowedAmount` exactly, so the result is bit-identical to
205
+ * recomputing every reserve value per position; reordering the divisions
206
+ * would round differently and can flip a verdict at the threshold.
207
+ */
208
+ export declare function computeObligationHealth(deposits: ObligationHealthDepositInput[], borrows: ObligationHealthBorrowInput[], reserveHealth: ReserveHealthTable): ObligationHealth;
209
+ export type ReserveDepositHealthValues = {
210
+ price: BigNumber;
211
+ cTokenRatio: BigNumber;
212
+ mintScale: BigNumber;
213
+ closeLtv: BigNumber;
214
+ };
215
+ export type ReserveBorrowHealthValues = {
216
+ price: BigNumber;
217
+ mintScale: BigNumber;
218
+ cumulativeBorrowRate: BigNumber;
219
+ borrowWeight: BigNumber;
220
+ };
221
+ /**
222
+ * Each lookup throws, as `findReserveOrThrow` does, when no reserve has
223
+ * this coin type. The two sides are separate so a reserve is only read for
224
+ * the fields its positions actually use.
225
+ */
226
+ export type ReserveHealthTable = {
227
+ deposit: (coinType: string) => ReserveDepositHealthValues;
228
+ borrow: (coinType: string) => ReserveBorrowHealthValues;
229
+ };
230
+ /**
231
+ * The per-reserve inputs to `computeObligationHealth`, derived once per
232
+ * reserve and side on first lookup instead of once per position. Build one
233
+ * per pass over a fixed set of priced reserves and drop it after: the values
234
+ * are a snapshot, and a caller that later rewrites a reserve's price in place
235
+ * would otherwise keep reading the old one.
201
236
  */
237
+ export declare function buildReserveHealthTable(reserves: Reserve<string>[]): ReserveHealthTable;
202
238
  export declare function computeObligationHealthFromPositions(deposits: ObligationHealthDepositInput[], borrows: ObligationHealthBorrowInput[], refreshedReserves: Reserve<string>[]): ObligationHealth;
package/utils/simulate.js CHANGED
@@ -507,28 +507,32 @@ export const compoundDebt = (borrow, reserve) => {
507
507
  * All math stays in BigNumber end-to-end — never construct a `Decimal` from
508
508
  * a stored quantity; `numberToDecimal`/`stringToDecimal` round through a JS
509
509
  * `number` (`Math.round`) and are lossy at WAD scale. `decimalToBigNumber` is
510
- * only ever called on a genuine on-chain `Reserve` field below.
510
+ * only ever called on a genuine on-chain `Reserve` field, in
511
+ * `buildReserveHealthTable`.
512
+ *
513
+ * Per-position operation order matches `getCTokenMarketValue` and
514
+ * `compoundBorrowedAmount` exactly, so the result is bit-identical to
515
+ * recomputing every reserve value per position; reordering the divisions
516
+ * would round differently and can flip a verdict at the threshold.
511
517
  */
512
- export function computeObligationHealthFromPositions(deposits, borrows, refreshedReserves) {
518
+ export function computeObligationHealth(deposits, borrows, reserveHealth) {
513
519
  let depositValueUsd = new BigNumber(0);
514
520
  let unhealthyBorrowValueUsd = new BigNumber(0);
515
521
  for (const deposit of deposits) {
516
- const reserve = findReserveOrThrow(refreshedReserves, deposit.coinType);
517
- const config = reserve.config.element;
518
- const marketValue = getCTokenMarketValue(reserve, deposit.depositedCtokenAmount);
522
+ const reserve = reserveHealth.deposit(deposit.coinType);
523
+ const marketValue = reserve.price
524
+ .multipliedBy(deposit.depositedCtokenAmount.multipliedBy(reserve.cTokenRatio))
525
+ .dividedBy(reserve.mintScale);
519
526
  depositValueUsd = depositValueUsd.plus(marketValue);
520
- unhealthyBorrowValueUsd = unhealthyBorrowValueUsd.plus(marketValue.multipliedBy(computeCloseLtv(config)));
527
+ unhealthyBorrowValueUsd = unhealthyBorrowValueUsd.plus(marketValue.multipliedBy(reserve.closeLtv));
521
528
  }
522
529
  let weightedBorrowedValueUsd = new BigNumber(0);
523
530
  for (const borrow of borrows) {
524
- const reserve = findReserveOrThrow(refreshedReserves, borrow.coinType);
525
- const config = reserve.config.element;
526
- const currentBorrowedAmountUnderlying = compoundBorrowedAmount(borrow.borrowedAmountUnderlying, decimalToBigNumber(reserve.cumulativeBorrowRate), borrow.cumulativeBorrowRateAtSnapshot);
527
- const marketValue = currentBorrowedAmountUnderlying
528
- .multipliedBy(decimalToBigNumber(reserve.price))
529
- .dividedBy(new BigNumber(10 ** reserve.mintDecimals));
530
- const borrowWeight = computeBorrowWeight(config);
531
- weightedBorrowedValueUsd = weightedBorrowedValueUsd.plus(marketValue.multipliedBy(borrowWeight));
531
+ const reserve = reserveHealth.borrow(borrow.coinType);
532
+ const marketValue = compoundBorrowedAmount(borrow.borrowedAmountUnderlying, reserve.cumulativeBorrowRate, borrow.cumulativeBorrowRateAtSnapshot)
533
+ .multipliedBy(reserve.price)
534
+ .dividedBy(reserve.mintScale);
535
+ weightedBorrowedValueUsd = weightedBorrowedValueUsd.plus(marketValue.multipliedBy(reserve.borrowWeight));
532
536
  }
533
537
  return {
534
538
  depositedValueUsd: depositValueUsd,
@@ -536,3 +540,46 @@ export function computeObligationHealthFromPositions(deposits, borrows, refreshe
536
540
  weightedBorrowedValueUsd,
537
541
  };
538
542
  }
543
+ const memoizeByCoinType = (derive) => {
544
+ const byCoinType = new Map();
545
+ return (coinType) => {
546
+ let values = byCoinType.get(coinType);
547
+ if (values === undefined) {
548
+ values = derive(coinType);
549
+ byCoinType.set(coinType, values);
550
+ }
551
+ return values;
552
+ };
553
+ };
554
+ /**
555
+ * The per-reserve inputs to `computeObligationHealth`, derived once per
556
+ * reserve and side on first lookup instead of once per position. Build one
557
+ * per pass over a fixed set of priced reserves and drop it after: the values
558
+ * are a snapshot, and a caller that later rewrites a reserve's price in place
559
+ * would otherwise keep reading the old one.
560
+ */
561
+ export function buildReserveHealthTable(reserves) {
562
+ return {
563
+ deposit: memoizeByCoinType((coinType) => {
564
+ const reserve = findReserveOrThrow(reserves, coinType);
565
+ return {
566
+ price: decimalToBigNumber(reserve.price),
567
+ cTokenRatio: cTokenRatio(reserve),
568
+ mintScale: new BigNumber(10 ** reserve.mintDecimals),
569
+ closeLtv: computeCloseLtv(reserve.config.element),
570
+ };
571
+ }),
572
+ borrow: memoizeByCoinType((coinType) => {
573
+ const reserve = findReserveOrThrow(reserves, coinType);
574
+ return {
575
+ price: decimalToBigNumber(reserve.price),
576
+ mintScale: new BigNumber(10 ** reserve.mintDecimals),
577
+ cumulativeBorrowRate: decimalToBigNumber(reserve.cumulativeBorrowRate),
578
+ borrowWeight: computeBorrowWeight(reserve.config.element),
579
+ };
580
+ }),
581
+ };
582
+ }
583
+ export function computeObligationHealthFromPositions(deposits, borrows, refreshedReserves) {
584
+ return computeObligationHealth(deposits, borrows, buildReserveHealthTable(refreshedReserves));
585
+ }