@suilend/sdk 12.2.0 → 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 +11 -3
- package/client.js +20 -12
- package/package.json +1 -1
- package/strategies.d.ts +1 -0
- package/strategies.js +6 -2
- package/utils/simulate.d.ts +37 -1
- package/utils/simulate.js +61 -14
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
|
|
509
|
-
|
|
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.
|
|
526
|
+
stalePriceIdentifiers.add(priceIdentifiers[+i]);
|
|
520
527
|
}
|
|
521
528
|
})()));
|
|
522
|
-
if (stalePriceIdentifiers.
|
|
523
|
-
const
|
|
524
|
-
await this.
|
|
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.
|
|
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.
|
|
2122
|
-
if (targetExposure.gt(1) &&
|
|
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;
|
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
|
+
}
|