@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 +14 -3
- package/client.js +70 -24
- package/lib/initialize.d.ts +8 -0
- package/package.json +1 -1
- package/parsers/lendingMarket.d.ts +1 -0
- package/parsers/obligation.d.ts +2 -0
- package/parsers/reserve.d.ts +2 -0
- package/parsers/reserve.js +4 -2
- package/strategies.d.ts +2 -0
- package/utils/simulate.d.ts +37 -1
- package/utils/simulate.js +61 -14
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
|
|
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
|
|
509
|
-
|
|
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.
|
|
564
|
+
stalePriceIdentifiers.add(priceIdentifiers[+i]);
|
|
520
565
|
}
|
|
521
566
|
})()));
|
|
522
|
-
if (stalePriceIdentifiers.
|
|
523
|
-
const
|
|
524
|
-
await this.
|
|
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,
|
package/lib/initialize.d.ts
CHANGED
|
@@ -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.
|
|
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;
|
package/parsers/obligation.d.ts
CHANGED
|
@@ -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;
|
package/parsers/reserve.d.ts
CHANGED
|
@@ -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;
|
package/parsers/reserve.js
CHANGED
|
@@ -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
|
-
|
|
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
|
-
|
|
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;
|
package/utils/simulate.d.ts
CHANGED
|
@@ -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
|
|
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
|
|
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
|
|
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 =
|
|
517
|
-
const
|
|
518
|
-
|
|
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(
|
|
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 =
|
|
525
|
-
const
|
|
526
|
-
|
|
527
|
-
|
|
528
|
-
|
|
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
|
+
}
|