@suilend/sdk 12.3.0-next.g8719037 → 12.3.0-next.g97f4281

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
@@ -2,12 +2,21 @@ import { SuiClientTypes } from "@mysten/sui/client";
2
2
  import { SuiGrpcClient } from "@mysten/sui/grpc";
3
3
  import { Transaction, 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;
@@ -71,6 +80,8 @@ export declare class SuilendClient {
71
80
  receivers: string[];
72
81
  weights: string[];
73
82
  }>;
83
+ /** A reserve's confidence multiplier `k`, in bps; pass `parsedReserve.config.additionalFields.id`. 0 when unset. */
84
+ static getReserveConfidenceMultiplierBps(suiGrpcClient: SuiGrpcClient, additionalFieldsBagId: string): Promise<BigNumber>;
74
85
  static createNewLendingMarket(registryId: string, lendingMarketType: string, transaction: Transaction): {
75
86
  NestedResult: [number, number];
76
87
  $kind: "NestedResult";
@@ -99,7 +110,7 @@ export declare class SuilendClient {
99
110
  updateRateLimiterConfig(lendingMarketOwnerCapId: string, transaction: Transaction, newRateLimiterConfigArgs: CreateRateLimiterConfigArgs): import("@mysten/sui/transactions").TransactionResult;
100
111
  changeReservePriceFeed(lendingMarketOwnerCapId: string, coinType: string, pythPriceId: string, transaction: Transaction): Promise<import("@mysten/sui/transactions").TransactionResult>;
101
112
  createObligation(transaction: Transaction): import("@mysten/sui/transactions").TransactionResult;
102
- refreshAll(transaction: Transaction, obligation?: Obligation<string>, coinTypes?: string[]): Promise<void>;
113
+ refreshAll(transaction: Transaction, obligation?: Obligation<string>, coinTypes?: string[], priceUpdate?: PriceUpdate): Promise<void>;
103
114
  refreshReservePrices(transaction: Transaction, priceInfoObjectId: string, reserveArrayIndex: bigint): Promise<void>;
104
115
  deposit(sendCoin: TransactionObjectInput, coinType: string, obligationOwnerCap: TransactionObjectInput, transaction: Transaction): void;
105
116
  depositIntoObligation(ownerId: string, coinType: string, value: string, transaction: Transaction, obligationOwnerCap: TransactionObjectInput): Promise<void>;
@@ -111,8 +122,8 @@ export declare class SuilendClient {
111
122
  borrowAndSendToUser(ownerId: string, obligationOwnerCap: TransactionObjectInput, obligationId: string, coinType: string, value: string, transaction: Transaction): Promise<void>;
112
123
  repay(obligationId: string, coinType: string, coin: TransactionObjectInput, transaction: Transaction): import("@mysten/sui/transactions").TransactionResult;
113
124
  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>;
125
+ liquidateAndRedeem(transaction: Transaction, obligation: Obligation<string>, repayCoinType: string, withdrawCoinType: string, repayCoinId: TransactionObjectInput, priceUpdate?: PriceUpdate): Promise<import("@mysten/sui/transactions").TransactionResult>;
126
+ liquidate(transaction: Transaction, obligation: Obligation<string>, repayCoinType: string, withdrawCoinType: string, repayCoinId: TransactionObjectInput, priceUpdate?: PriceUpdate): Promise<import("@mysten/sui/transactions").TransactionResult>;
116
127
  migrate(transaction: Transaction, lendingMarketOwnerCapId: string): import("@mysten/sui/transactions").TransactionResult;
117
128
  claimFees(transaction: Transaction, coinType: string): import("@mysten/sui/transactions").TransactionResult;
118
129
  setFeeReceiversAndWeights(transaction: Transaction, lendingMarketOwnerCapId: string, receivers: string[], weights: bigint[]): import("@mysten/sui/transactions").TransactionResult;
package/client.js CHANGED
@@ -1,5 +1,7 @@
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";
@@ -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,42 @@ export async function listAllOwnedObjects(suiGrpcClient, owner, type, include) {
153
156
  cache.set(key, promise);
154
157
  return promise;
155
158
  }
159
+ // Only safe for a key with a fixed, known type tag; see findBagEntryByTypeSuffix
160
+ // for one that can legitimately be absent.
161
+ async function resolveDynamicFieldId(suiGrpcClient, parentId, nameType) {
162
+ const { dynamicField } = await suiGrpcClient.getDynamicField({
163
+ parentId,
164
+ name: { type: nameType, bcs: new Uint8Array([0]) },
165
+ });
166
+ return dynamicField.fieldId;
167
+ }
168
+ // For a key that's always present (getFeeReceivers), both calls' errors can
169
+ // propagate through this combined helper unguarded.
170
+ async function fetchDynamicFieldJson(suiGrpcClient, parentId, nameType) {
171
+ const fieldId = await resolveDynamicFieldId(suiGrpcClient, parentId, nameType);
172
+ const { object } = await suiGrpcClient.getObject({
173
+ objectId: fieldId,
174
+ include: { json: true },
175
+ });
176
+ return object.json;
177
+ }
178
+ // Scans by type-tag suffix, ignoring the package address: codegen doesn't
179
+ // track a struct's originally-defining address, only the current PUBLISHED_AT.
180
+ async function findBagEntryByTypeSuffix(suiGrpcClient, parentId, typeSuffix) {
181
+ let cursor = null;
182
+ while (true) {
183
+ const response = await suiGrpcClient.listDynamicFields({ parentId, cursor });
184
+ const match = response.dynamicFields.find((entry) => entry.name.type.endsWith(typeSuffix));
185
+ if (match)
186
+ return match;
187
+ if (!response.hasNextPage)
188
+ return undefined;
189
+ if (!response.cursor) {
190
+ throw new Error(`listDynamicFields for ${parentId} reported hasNextPage=true with no cursor to continue`);
191
+ }
192
+ cursor = response.cursor;
193
+ }
194
+ }
156
195
  export class SuilendClient {
157
196
  lendingMarket;
158
197
  suiGrpcClient;
@@ -249,22 +288,22 @@ export class SuilendClient {
249
288
  });
250
289
  }
251
290
  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;
291
+ const json = await fetchDynamicFieldJson(suiGrpcClient, lendingMarketId, `${PKG_V10}::lending_market::FeeReceiversKey`);
264
292
  const receivers = json?.value?.receivers;
265
293
  const weights = json?.value?.weights;
266
294
  return { receivers, weights };
267
295
  }
296
+ /** A reserve's confidence multiplier `k`, in bps; pass `parsedReserve.config.additionalFields.id`. 0 when unset. */
297
+ static async getReserveConfidenceMultiplierBps(suiGrpcClient, additionalFieldsBagId) {
298
+ const match = await findBagEntryByTypeSuffix(suiGrpcClient, additionalFieldsBagId, "::reserve_config::ConfidenceMultiplierKey");
299
+ if (!match)
300
+ return new BigNumber(0);
301
+ const { dynamicField } = await suiGrpcClient.getDynamicField({
302
+ parentId: additionalFieldsBagId,
303
+ name: match.name,
304
+ });
305
+ return new BigNumber(bcs.u64().parse(dynamicField.value.bcs));
306
+ }
268
307
  static createNewLendingMarket(registryId, lendingMarketType, transaction) {
269
308
  const [ownerCap, lendingMarket] = createLendingMarket(transaction, lendingMarketType, transaction.object(registryId));
270
309
  transaction.moveCall({
@@ -475,7 +514,7 @@ export class SuilendClient {
475
514
  typeArguments: this.lendingMarket.$typeArgs,
476
515
  });
477
516
  }
478
- async refreshAll(transaction, obligation, coinTypes) {
517
+ async refreshAll(transaction, obligation, coinTypes, priceUpdate) {
479
518
  const reserveArrayIndexToPriceId = new Map();
480
519
  if (obligation) {
481
520
  obligation.deposits.forEach((deposit) => {
@@ -505,8 +544,14 @@ export class SuilendClient {
505
544
  const priceInfoObjectId = await this.pythClient.getPriceFeedObjectId(priceIdentifiers[i]);
506
545
  priceInfoObjectIds[i] = priceInfoObjectId;
507
546
  })()));
508
- const stalePriceIdentifiers = [];
509
- await Promise.all(Object.entries(priceInfoObjectIds).map(([i, priceInfoObjectId]) => (async () => {
547
+ const suppliedPriceIdentifiers = new Set(priceUpdate?.priceIds.map(normalizePriceIdentifier));
548
+ if (priceUpdate?.priceIds.length) {
549
+ await this.pythClient.updatePriceFeeds(transaction, priceUpdate.updateData, priceUpdate.priceIds);
550
+ }
551
+ const stalePriceIdentifiers = new Set();
552
+ await Promise.all(Object.entries(priceInfoObjectIds)
553
+ .filter(([i]) => !suppliedPriceIdentifiers.has(normalizePriceIdentifier(priceIdentifiers[+i])))
554
+ .map(([i, priceInfoObjectId]) => (async () => {
510
555
  // Read the field directly: the generated parser's type check is
511
556
  // bound to one Pyth package address and would throw post-cutover.
512
557
  const { object } = await this.suiGrpcClient.getObject({
@@ -516,15 +561,16 @@ export class SuilendClient {
516
561
  const publishTime = priceInfoPublishTime(object.json, priceInfoObjectId);
517
562
  const stalenessSeconds = Date.now() / 1000 - Number(publishTime);
518
563
  if (needsPriceRefresh(stalenessSeconds)) {
519
- stalePriceIdentifiers.push(priceIdentifiers[+i]);
564
+ stalePriceIdentifiers.add(priceIdentifiers[+i]);
520
565
  }
521
566
  })()));
522
- if (stalePriceIdentifiers.length > 0) {
523
- const stalePriceUpdateData = await this.pythConnection.getPriceFeedsUpdateData(stalePriceIdentifiers);
524
- await this.pythClient.updatePriceFeeds(transaction, stalePriceUpdateData, stalePriceIdentifiers);
567
+ if (stalePriceIdentifiers.size > 0) {
568
+ const staleIds = [...stalePriceIdentifiers];
569
+ const stalePriceUpdateData = await this.pythConnection.getPriceFeedsUpdateData(staleIds);
570
+ await this.pythClient.updatePriceFeeds(transaction, stalePriceUpdateData, staleIds);
525
571
  }
526
572
  for (let i = 0; i < reserveArrayIndexes.length; i++) {
527
- this.refreshReservePrices(transaction, priceInfoObjectIds[i], reserveArrayIndexes[i]);
573
+ await this.refreshReservePrices(transaction, priceInfoObjectIds[i], reserveArrayIndexes[i]);
528
574
  }
529
575
  }
530
576
  async refreshReservePrices(transaction, priceInfoObjectId, reserveArrayIndex) {
@@ -670,8 +716,8 @@ export class SuilendClient {
670
716
  transaction.transferObjects([sendCoin], transaction.pure.address(ownerId));
671
717
  return result;
672
718
  }
673
- async liquidateAndRedeem(transaction, obligation, repayCoinType, withdrawCoinType, repayCoinId) {
674
- const [ctokens, exemption] = await this.liquidate(transaction, obligation, repayCoinType, withdrawCoinType, repayCoinId);
719
+ async liquidateAndRedeem(transaction, obligation, repayCoinType, withdrawCoinType, repayCoinId, priceUpdate) {
720
+ const [ctokens, exemption] = await this.liquidate(transaction, obligation, repayCoinType, withdrawCoinType, repayCoinId, priceUpdate);
675
721
  const [optionalExemption] = transaction.moveCall({
676
722
  target: `0x1::option::some`,
677
723
  typeArguments: [
@@ -681,8 +727,8 @@ export class SuilendClient {
681
727
  });
682
728
  return this.redeem(ctokens, withdrawCoinType, optionalExemption, transaction);
683
729
  }
684
- async liquidate(transaction, obligation, repayCoinType, withdrawCoinType, repayCoinId) {
685
- await this.refreshAll(transaction, obligation);
730
+ async liquidate(transaction, obligation, repayCoinType, withdrawCoinType, repayCoinId, priceUpdate) {
731
+ await this.refreshAll(transaction, obligation, undefined, priceUpdate);
686
732
  return liquidate(transaction, [this.lendingMarket.$typeArgs[0], repayCoinType, withdrawCoinType], {
687
733
  lendingMarket: transaction.object(this.lendingMarket.id),
688
734
  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.g8719037","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.g97f4281","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;
@@ -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
+ }