@suilend/sdk 12.3.0 → 12.4.0-next.gd4b87be
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/_generated/suilend/lending-market/functions.d.ts +1 -0
- package/_generated/suilend/lending-market/functions.js +11 -0
- package/_generated/suilend/reserve-config/functions.d.ts +5 -0
- package/_generated/suilend/reserve-config/functions.js +15 -0
- package/client.d.ts +25 -4
- package/client.js +140 -28
- 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 +7 -3
- package/strategies.js +188 -32
- package/utils/simulate.d.ts +37 -1
- package/utils/simulate.js +61 -14
|
@@ -58,6 +58,7 @@ export interface ClosePoolRewardArgs {
|
|
|
58
58
|
}
|
|
59
59
|
export declare function closePoolReward(tx: Transaction, typeArgs: [string, string], args: ClosePoolRewardArgs): import("@mysten/sui/transactions").TransactionResult;
|
|
60
60
|
export declare function reserve(tx: Transaction, typeArgs: [string, string], lendingMarket: TransactionObjectInput): import("@mysten/sui/transactions").TransactionResult;
|
|
61
|
+
export declare function reserveConfigBuilder(tx: Transaction, typeArgs: [string, string], lendingMarket: TransactionObjectInput): import("@mysten/sui/transactions").TransactionResult;
|
|
61
62
|
export interface CompoundInterestArgs {
|
|
62
63
|
lendingMarket: TransactionObjectInput;
|
|
63
64
|
reserveArrayIndex: bigint | TransactionArgument;
|
|
@@ -107,6 +107,17 @@ export function reserve(tx, typeArgs, lendingMarket) {
|
|
|
107
107
|
arguments: [obj(tx, lendingMarket)],
|
|
108
108
|
});
|
|
109
109
|
}
|
|
110
|
+
// lending_market::reserve_config_builder has no published package to
|
|
111
|
+
// regenerate a binding from yet, so this is hand-written to match the shape
|
|
112
|
+
// codegen already produces for `reserve` above. Replace it with the real
|
|
113
|
+
// generated output once this package publishes and the generator runs again.
|
|
114
|
+
export function reserveConfigBuilder(tx, typeArgs, lendingMarket) {
|
|
115
|
+
return tx.moveCall({
|
|
116
|
+
target: `${PUBLISHED_AT}::lending_market::reserve_config_builder`,
|
|
117
|
+
typeArguments: typeArgs,
|
|
118
|
+
arguments: [obj(tx, lendingMarket)],
|
|
119
|
+
});
|
|
120
|
+
}
|
|
110
121
|
export function compoundInterest(tx, typeArg, args) {
|
|
111
122
|
return tx.moveCall({
|
|
112
123
|
target: `${PUBLISHED_AT}::lending_market::compound_interest`,
|
|
@@ -13,6 +13,11 @@ export interface SetSpreadFeeBpsArgs {
|
|
|
13
13
|
spreadFeeBps: bigint | TransactionArgument;
|
|
14
14
|
}
|
|
15
15
|
export declare function setSpreadFeeBps(tx: Transaction, args: SetSpreadFeeBpsArgs): import("@mysten/sui/transactions").TransactionResult;
|
|
16
|
+
export interface SetConfidenceMultiplierBuilderArgs {
|
|
17
|
+
builder: TransactionObjectInput;
|
|
18
|
+
confidenceMultiplierBps: bigint | TransactionArgument;
|
|
19
|
+
}
|
|
20
|
+
export declare function setConfidenceMultiplierBuilder(tx: Transaction, args: SetConfidenceMultiplierBuilderArgs): import("@mysten/sui/transactions").TransactionResult;
|
|
16
21
|
export declare function spreadFee(tx: Transaction, config: TransactionObjectInput): import("@mysten/sui/transactions").TransactionResult;
|
|
17
22
|
export declare function borrowFee(tx: Transaction, config: TransactionObjectInput): import("@mysten/sui/transactions").TransactionResult;
|
|
18
23
|
export declare function borrowLimit(tx: Transaction, config: TransactionObjectInput): import("@mysten/sui/transactions").TransactionResult;
|
|
@@ -29,6 +29,21 @@ export function setSpreadFeeBps(tx, args) {
|
|
|
29
29
|
arguments: [obj(tx, args.builder), pure(tx, args.spreadFeeBps, `u64`)],
|
|
30
30
|
});
|
|
31
31
|
}
|
|
32
|
+
// reserve_config::set_confidence_multiplier_builder is published (v25), but
|
|
33
|
+
// this SDK's PUBLISHED_AT/PKG_V* constants haven't been bumped to v25 yet, so
|
|
34
|
+
// there's nothing to regenerate a binding from until that separate regen
|
|
35
|
+
// pass happens. Hand-written to match the shape codegen already produces for
|
|
36
|
+
// every other u64 builder setter in this file (e.g. setSpreadFeeBps above).
|
|
37
|
+
// Replace it with the real generated output once that regen lands.
|
|
38
|
+
export function setConfidenceMultiplierBuilder(tx, args) {
|
|
39
|
+
return tx.moveCall({
|
|
40
|
+
target: `${PUBLISHED_AT}::reserve_config::set_confidence_multiplier_builder`,
|
|
41
|
+
arguments: [
|
|
42
|
+
obj(tx, args.builder),
|
|
43
|
+
pure(tx, args.confidenceMultiplierBps, `u64`),
|
|
44
|
+
],
|
|
45
|
+
});
|
|
46
|
+
}
|
|
32
47
|
export function spreadFee(tx, config) {
|
|
33
48
|
return tx.moveCall({
|
|
34
49
|
target: `${PUBLISHED_AT}::reserve_config::spread_fee`,
|
package/client.d.ts
CHANGED
|
@@ -1,13 +1,22 @@
|
|
|
1
1
|
import { SuiClientTypes } from "@mysten/sui/client";
|
|
2
2
|
import { SuiGrpcClient } from "@mysten/sui/grpc";
|
|
3
|
-
import { Transaction, TransactionObjectArgument, TransactionObjectInput } from "@mysten/sui/transactions";
|
|
3
|
+
import { Transaction, TransactionArgument, TransactionObjectArgument, TransactionObjectInput } from "@mysten/sui/transactions";
|
|
4
4
|
import { SuiPythClient } from "@pythnetwork/pyth-sui-js";
|
|
5
|
+
import BigNumber from "bignumber.js";
|
|
5
6
|
import { LendingMarket, ObligationOwnerCap } from "./_generated/suilend/lending-market/structs";
|
|
6
7
|
import { Obligation } from "./_generated/suilend/obligation/structs";
|
|
7
8
|
import { NewConfigArgs as CreateRateLimiterConfigArgs } from "./_generated/suilend/rate-limiter/functions";
|
|
8
9
|
import { CreateReserveConfigArgs } from "./_generated/suilend/reserve-config/functions";
|
|
9
10
|
import { type PriceUpdateDataSource } from "./lib/pyth";
|
|
10
11
|
import { Side } from "./lib/types";
|
|
12
|
+
/**
|
|
13
|
+
* Signed Pyth update data the caller already fetched for `priceIds`, posted
|
|
14
|
+
* unconditionally instead of the on-chain staleness check deciding.
|
|
15
|
+
*/
|
|
16
|
+
export interface PriceUpdate {
|
|
17
|
+
priceIds: string[];
|
|
18
|
+
updateData: Buffer[];
|
|
19
|
+
}
|
|
11
20
|
export declare function needsPriceRefresh(stalenessSeconds: number): boolean;
|
|
12
21
|
export declare const ADMIN_ADDRESS: string;
|
|
13
22
|
export declare const LENDING_MARKET_REGISTRY_ID: string;
|
|
@@ -41,6 +50,9 @@ export type ClaimRewardsReward = {
|
|
|
41
50
|
rewardCoinType: string;
|
|
42
51
|
side: Side;
|
|
43
52
|
};
|
|
53
|
+
export type PatchReserveConfigArgs = Partial<CreateReserveConfigArgs> & {
|
|
54
|
+
confidenceMultiplierBps?: bigint | TransactionArgument;
|
|
55
|
+
};
|
|
44
56
|
export declare class SuilendClient {
|
|
45
57
|
lendingMarket: LendingMarket<string>;
|
|
46
58
|
suiGrpcClient: SuiGrpcClient;
|
|
@@ -71,6 +83,8 @@ export declare class SuilendClient {
|
|
|
71
83
|
receivers: string[];
|
|
72
84
|
weights: string[];
|
|
73
85
|
}>;
|
|
86
|
+
/** A reserve's confidence multiplier `k`, in bps; pass `parsedReserve.config.additionalFields.id`. 0 when unset. */
|
|
87
|
+
static getReserveConfidenceMultiplierBps(suiGrpcClient: SuiGrpcClient, additionalFieldsBagId: string): Promise<BigNumber>;
|
|
74
88
|
static createNewLendingMarket(registryId: string, lendingMarketType: string, transaction: Transaction): {
|
|
75
89
|
NestedResult: [number, number];
|
|
76
90
|
$kind: "NestedResult";
|
|
@@ -94,12 +108,19 @@ export declare class SuilendClient {
|
|
|
94
108
|
claimRewardsAndSendToUser(ownerId: string, obligationOwnerCap: TransactionObjectInput, rewards: ClaimRewardsReward[], transaction: Transaction): Transaction;
|
|
95
109
|
claimRewardsAndDeposit(ownerId: string, obligationOwnerCap: TransactionObjectInput, rewards: ClaimRewardsReward[], transaction: Transaction): Transaction;
|
|
96
110
|
findReserveArrayIndex(coinType: string): bigint;
|
|
111
|
+
private submitReserveConfigUpdate;
|
|
97
112
|
updateReserveConfig(lendingMarketOwnerCapId: string, transaction: Transaction, coinType: string, createReserveConfigArgs: CreateReserveConfigArgs): import("@mysten/sui/transactions").TransactionResult;
|
|
113
|
+
/**
|
|
114
|
+
* Updates a subset of a reserve's config fields, leaving every unspecified
|
|
115
|
+
* field -- including `additionalFields` entries like the confidence
|
|
116
|
+
* multiplier -- unchanged from the reserve's current on-chain value.
|
|
117
|
+
*/
|
|
118
|
+
patchReserveConfig(lendingMarketOwnerCapId: string, transaction: Transaction, coinType: string, overrides: PatchReserveConfigArgs): import("@mysten/sui/transactions").TransactionResult;
|
|
98
119
|
newObligationOwnerCap(transaction: Transaction, lendingMarketOwnerCapId: string, destinationAddress: string, obligationId: string): void;
|
|
99
120
|
updateRateLimiterConfig(lendingMarketOwnerCapId: string, transaction: Transaction, newRateLimiterConfigArgs: CreateRateLimiterConfigArgs): import("@mysten/sui/transactions").TransactionResult;
|
|
100
121
|
changeReservePriceFeed(lendingMarketOwnerCapId: string, coinType: string, pythPriceId: string, transaction: Transaction): Promise<import("@mysten/sui/transactions").TransactionResult>;
|
|
101
122
|
createObligation(transaction: Transaction): import("@mysten/sui/transactions").TransactionResult;
|
|
102
|
-
refreshAll(transaction: Transaction, obligation?: Obligation<string>, coinTypes?: string[]): Promise<void>;
|
|
123
|
+
refreshAll(transaction: Transaction, obligation?: Obligation<string>, coinTypes?: string[], priceUpdate?: PriceUpdate): Promise<void>;
|
|
103
124
|
refreshReservePrices(transaction: Transaction, priceInfoObjectId: string, reserveArrayIndex: bigint): Promise<void>;
|
|
104
125
|
deposit(sendCoin: TransactionObjectInput, coinType: string, obligationOwnerCap: TransactionObjectInput, transaction: Transaction): void;
|
|
105
126
|
depositIntoObligation(ownerId: string, coinType: string, value: string, transaction: Transaction, obligationOwnerCap: TransactionObjectInput): Promise<void>;
|
|
@@ -111,8 +132,8 @@ export declare class SuilendClient {
|
|
|
111
132
|
borrowAndSendToUser(ownerId: string, obligationOwnerCap: TransactionObjectInput, obligationId: string, coinType: string, value: string, transaction: Transaction): Promise<void>;
|
|
112
133
|
repay(obligationId: string, coinType: string, coin: TransactionObjectInput, transaction: Transaction): import("@mysten/sui/transactions").TransactionResult;
|
|
113
134
|
repayIntoObligation(ownerId: string, obligationId: string, coinType: string, value: string, transaction: Transaction): Promise<import("@mysten/sui/transactions").TransactionResult>;
|
|
114
|
-
liquidateAndRedeem(transaction: Transaction, obligation: Obligation<string>, repayCoinType: string, withdrawCoinType: string, repayCoinId: TransactionObjectInput): Promise<import("@mysten/sui/transactions").TransactionResult>;
|
|
115
|
-
liquidate(transaction: Transaction, obligation: Obligation<string>, repayCoinType: string, withdrawCoinType: string, repayCoinId: TransactionObjectInput): Promise<import("@mysten/sui/transactions").TransactionResult>;
|
|
135
|
+
liquidateAndRedeem(transaction: Transaction, obligation: Obligation<string>, repayCoinType: string, withdrawCoinType: string, repayCoinId: TransactionObjectInput, priceUpdate?: PriceUpdate): Promise<import("@mysten/sui/transactions").TransactionResult>;
|
|
136
|
+
liquidate(transaction: Transaction, obligation: Obligation<string>, repayCoinType: string, withdrawCoinType: string, repayCoinId: TransactionObjectInput, priceUpdate?: PriceUpdate): Promise<import("@mysten/sui/transactions").TransactionResult>;
|
|
116
137
|
migrate(transaction: Transaction, lendingMarketOwnerCapId: string): import("@mysten/sui/transactions").TransactionResult;
|
|
117
138
|
claimFees(transaction: Transaction, coinType: string): import("@mysten/sui/transactions").TransactionResult;
|
|
118
139
|
setFeeReceiversAndWeights(transaction: Transaction, lendingMarketOwnerCapId: string, receivers: string[], weights: bigint[]): import("@mysten/sui/transactions").TransactionResult;
|
package/client.js
CHANGED
|
@@ -1,15 +1,17 @@
|
|
|
1
|
+
import { bcs } from "@mysten/sui/bcs";
|
|
1
2
|
import { SUI_CLOCK_OBJECT_ID, SUI_SYSTEM_STATE_OBJECT_ID, normalizeStructTag, normalizeSuiObjectId, toHex, } from "@mysten/sui/utils";
|
|
2
3
|
import { SuiPythClient } from "@pythnetwork/pyth-sui-js";
|
|
4
|
+
import BigNumber from "bignumber.js";
|
|
3
5
|
import { extractCTokenCoinType, getAllCoins, getSpendableCoin, } from "@suilend/sui-core";
|
|
4
6
|
import { phantom } from "./_generated/_framework/reified.js";
|
|
5
7
|
import { PKG_V10, setPublishedAt } from "./_generated/suilend/index.js";
|
|
6
8
|
import { PACKAGE_ID, PUBLISHED_AT } from "./_generated/suilend/index.js";
|
|
7
|
-
import { addPoolReward, borrowRequest, cancelPoolReward, claimFees, claimRewards, claimRewardsAndDeposit, closePoolReward, depositCtokensIntoObligation, depositLiquidityAndMintCtokens, fulfillLiquidityRequest, liquidate, migrate, newObligationOwnerCap, rebalanceStaker, redeemCtokensAndWithdrawLiquidityRequest, repay, setFeeReceivers, unstakeSuiFromStaker, updateRateLimiterConfig, updateReserveConfig, withdrawCtokens, } from "./_generated/suilend/lending-market/functions.js";
|
|
9
|
+
import { addPoolReward, borrowRequest, cancelPoolReward, claimFees, claimRewards, claimRewardsAndDeposit, closePoolReward, depositCtokensIntoObligation, depositLiquidityAndMintCtokens, fulfillLiquidityRequest, liquidate, migrate, newObligationOwnerCap, rebalanceStaker, redeemCtokensAndWithdrawLiquidityRequest, repay, reserveConfigBuilder, setFeeReceivers, unstakeSuiFromStaker, updateRateLimiterConfig, updateReserveConfig, withdrawCtokens, } from "./_generated/suilend/lending-market/functions.js";
|
|
8
10
|
import { LendingMarket, ObligationOwnerCap, } from "./_generated/suilend/lending-market/structs.js";
|
|
9
11
|
import { createLendingMarket } from "./_generated/suilend/lending-market-registry/functions.js";
|
|
10
12
|
import { Obligation } from "./_generated/suilend/obligation/structs.js";
|
|
11
13
|
import { newConfig as createRateLimiterConfig, } from "./_generated/suilend/rate-limiter/functions.js";
|
|
12
|
-
import { createReserveConfig, } from "./_generated/suilend/reserve-config/functions.js";
|
|
14
|
+
import { build as buildReserveConfig, createReserveConfig, setBorrowFeeBps, setBorrowLimit, setBorrowLimitUsd, setBorrowWeightBps, setCloseAttributedBorrowLimitUsd, setCloseLtvPct, setConfidenceMultiplierBuilder, setDepositLimit, setDepositLimitUsd, setInterestRateAprs, setInterestRateUtils, setIsolated, setLiquidationBonusBps, setMaxCloseLtvPct, setMaxLiquidationBonusBps, setOpenAttributedBorrowLimitUsd, setOpenLtvPct, setProtocolLiquidationFeeBps, setSpreadFeeBps, } from "./_generated/suilend/reserve-config/functions.js";
|
|
13
15
|
import { addReserveProCompatible, addReserveV2ProCompatible, changeReservePriceFeedProCompatible, priceInfoPublishTime, refreshReservePriceProCompatible, } from "./lib/proCompatible.js";
|
|
14
16
|
import { resolvePriceUpdateDataSource, } from "./lib/pyth.js";
|
|
15
17
|
import { Side } from "./lib/types.js";
|
|
@@ -28,6 +30,7 @@ const isSui = (coinType) => normalizeStructTag(coinType) === NORMALIZED_SUI_COIN
|
|
|
28
30
|
// refresh would come from a package the contract does not accept.
|
|
29
31
|
const WORMHOLE_STATE_ID = "0xdbca52b9fb4f712e25f61f974586d93ac541bcf8389564f0323bb07215168b5c";
|
|
30
32
|
const PYTH_STATE_ID = "0x03719fae774ddab3cfcaa53bbc046f0cbe21410019b6280811bf3f9f4b05839d";
|
|
33
|
+
const normalizePriceIdentifier = (id) => id.toLowerCase().replace(/^0x/, "");
|
|
31
34
|
// Mirrors suilend::oracles's MAX_FORWARD_SECONDS (move/contracts/suilend/sources/oracles.move);
|
|
32
35
|
// `refresh_reserve_price` now rejects a forward-dated price the same as a stale one.
|
|
33
36
|
const MAX_FORWARD_SECONDS = 60;
|
|
@@ -153,6 +156,87 @@ export async function listAllOwnedObjects(suiGrpcClient, owner, type, include) {
|
|
|
153
156
|
cache.set(key, promise);
|
|
154
157
|
return promise;
|
|
155
158
|
}
|
|
159
|
+
// One entry per CreateReserveConfigArgs field, each just closing over its
|
|
160
|
+
// generated setter -- lets patchReserveConfig apply only the caller's
|
|
161
|
+
// specified overrides with one loop instead of one copy-pasted
|
|
162
|
+
// `if (overrides.x !== undefined) setX(...)` per field.
|
|
163
|
+
const CREATE_RESERVE_CONFIG_FIELD_SETTERS = {
|
|
164
|
+
openLtvPct: (tx, builder, openLtvPct) => setOpenLtvPct(tx, { builder, openLtvPct }),
|
|
165
|
+
closeLtvPct: (tx, builder, closeLtvPct) => setCloseLtvPct(tx, { builder, closeLtvPct }),
|
|
166
|
+
maxCloseLtvPct: (tx, builder, maxCloseLtvPct) => setMaxCloseLtvPct(tx, { builder, maxCloseLtvPct }),
|
|
167
|
+
borrowWeightBps: (tx, builder, borrowWeightBps) => setBorrowWeightBps(tx, { builder, borrowWeightBps }),
|
|
168
|
+
depositLimit: (tx, builder, depositLimit) => setDepositLimit(tx, { builder, depositLimit }),
|
|
169
|
+
borrowLimit: (tx, builder, borrowLimit) => setBorrowLimit(tx, { builder, borrowLimit }),
|
|
170
|
+
liquidationBonusBps: (tx, builder, liquidationBonusBps) => setLiquidationBonusBps(tx, { builder, liquidationBonusBps }),
|
|
171
|
+
maxLiquidationBonusBps: (tx, builder, maxLiquidationBonusBps) => setMaxLiquidationBonusBps(tx, { builder, maxLiquidationBonusBps }),
|
|
172
|
+
depositLimitUsd: (tx, builder, depositLimitUsd) => setDepositLimitUsd(tx, { builder, depositLimitUsd }),
|
|
173
|
+
borrowLimitUsd: (tx, builder, borrowLimitUsd) => setBorrowLimitUsd(tx, { builder, borrowLimitUsd }),
|
|
174
|
+
borrowFeeBps: (tx, builder, borrowFeeBps) => setBorrowFeeBps(tx, { builder, borrowFeeBps }),
|
|
175
|
+
spreadFeeBps: (tx, builder, spreadFeeBps) => setSpreadFeeBps(tx, { builder, spreadFeeBps }),
|
|
176
|
+
protocolLiquidationFeeBps: (tx, builder, protocolLiquidationFeeBps) => setProtocolLiquidationFeeBps(tx, { builder, protocolLiquidationFeeBps }),
|
|
177
|
+
interestRateUtils: (tx, builder, interestRateUtils) => setInterestRateUtils(tx, { builder, interestRateUtils }),
|
|
178
|
+
interestRateAprs: (tx, builder, interestRateAprs) => setInterestRateAprs(tx, { builder, interestRateAprs }),
|
|
179
|
+
isolated: (tx, builder, isolated) => setIsolated(tx, { builder, isolated }),
|
|
180
|
+
openAttributedBorrowLimitUsd: (tx, builder, openAttributedBorrowLimitUsd) => setOpenAttributedBorrowLimitUsd(tx, {
|
|
181
|
+
builder,
|
|
182
|
+
openAttributedBorrowLimitUsd,
|
|
183
|
+
}),
|
|
184
|
+
closeAttributedBorrowLimitUsd: (tx, builder, closeAttributedBorrowLimitUsd) => setCloseAttributedBorrowLimitUsd(tx, {
|
|
185
|
+
builder,
|
|
186
|
+
closeAttributedBorrowLimitUsd,
|
|
187
|
+
}),
|
|
188
|
+
};
|
|
189
|
+
// A generic type parameter, unlike indexing CREATE_RESERVE_CONFIG_FIELD_SETTERS
|
|
190
|
+
// with the union `keyof CreateReserveConfigArgs` directly at the call site,
|
|
191
|
+
// keeps TS from expanding the call into one union branch per field (which
|
|
192
|
+
// blows up into "union type too complex to represent").
|
|
193
|
+
function applyCreateReserveConfigFieldOverride(tx, builder, overrides, key) {
|
|
194
|
+
const value = overrides[key];
|
|
195
|
+
if (value === undefined)
|
|
196
|
+
return;
|
|
197
|
+
// TS can't verify this call is sound (calling a mapped type indexed by a
|
|
198
|
+
// generic key produces a union of all 18 setter signatures, which blows up
|
|
199
|
+
// into "union type too complex to represent" rather than typechecking) --
|
|
200
|
+
// but it is: `value`'s type and CREATE_RESERVE_CONFIG_FIELD_SETTERS[key]'s
|
|
201
|
+
// parameter type are both CreateReserveConfigArgs[K] for the same K.
|
|
202
|
+
CREATE_RESERVE_CONFIG_FIELD_SETTERS[key](tx, builder, value);
|
|
203
|
+
}
|
|
204
|
+
// Only safe for a key with a fixed, known type tag; see findBagEntryByTypeSuffix
|
|
205
|
+
// for one that can legitimately be absent.
|
|
206
|
+
async function resolveDynamicFieldId(suiGrpcClient, parentId, nameType) {
|
|
207
|
+
const { dynamicField } = await suiGrpcClient.getDynamicField({
|
|
208
|
+
parentId,
|
|
209
|
+
name: { type: nameType, bcs: new Uint8Array([0]) },
|
|
210
|
+
});
|
|
211
|
+
return dynamicField.fieldId;
|
|
212
|
+
}
|
|
213
|
+
// For a key that's always present (getFeeReceivers), both calls' errors can
|
|
214
|
+
// propagate through this combined helper unguarded.
|
|
215
|
+
async function fetchDynamicFieldJson(suiGrpcClient, parentId, nameType) {
|
|
216
|
+
const fieldId = await resolveDynamicFieldId(suiGrpcClient, parentId, nameType);
|
|
217
|
+
const { object } = await suiGrpcClient.getObject({
|
|
218
|
+
objectId: fieldId,
|
|
219
|
+
include: { json: true },
|
|
220
|
+
});
|
|
221
|
+
return object.json;
|
|
222
|
+
}
|
|
223
|
+
// Scans by type-tag suffix, ignoring the package address: codegen doesn't
|
|
224
|
+
// track a struct's originally-defining address, only the current PUBLISHED_AT.
|
|
225
|
+
async function findBagEntryByTypeSuffix(suiGrpcClient, parentId, typeSuffix) {
|
|
226
|
+
let cursor = null;
|
|
227
|
+
while (true) {
|
|
228
|
+
const response = await suiGrpcClient.listDynamicFields({ parentId, cursor });
|
|
229
|
+
const match = response.dynamicFields.find((entry) => entry.name.type.endsWith(typeSuffix));
|
|
230
|
+
if (match)
|
|
231
|
+
return match;
|
|
232
|
+
if (!response.hasNextPage)
|
|
233
|
+
return undefined;
|
|
234
|
+
if (!response.cursor) {
|
|
235
|
+
throw new Error(`listDynamicFields for ${parentId} reported hasNextPage=true with no cursor to continue`);
|
|
236
|
+
}
|
|
237
|
+
cursor = response.cursor;
|
|
238
|
+
}
|
|
239
|
+
}
|
|
156
240
|
export class SuilendClient {
|
|
157
241
|
lendingMarket;
|
|
158
242
|
suiGrpcClient;
|
|
@@ -249,22 +333,22 @@ export class SuilendClient {
|
|
|
249
333
|
});
|
|
250
334
|
}
|
|
251
335
|
static async getFeeReceivers(suiGrpcClient, lendingMarketId) {
|
|
252
|
-
const
|
|
253
|
-
parentId: lendingMarketId,
|
|
254
|
-
name: {
|
|
255
|
-
type: `${PKG_V10}::lending_market::FeeReceiversKey`,
|
|
256
|
-
bcs: new Uint8Array([0]),
|
|
257
|
-
},
|
|
258
|
-
});
|
|
259
|
-
const { object } = await suiGrpcClient.getObject({
|
|
260
|
-
objectId: dynamicField.fieldId,
|
|
261
|
-
include: { json: true },
|
|
262
|
-
});
|
|
263
|
-
const json = object.json;
|
|
336
|
+
const json = await fetchDynamicFieldJson(suiGrpcClient, lendingMarketId, `${PKG_V10}::lending_market::FeeReceiversKey`);
|
|
264
337
|
const receivers = json?.value?.receivers;
|
|
265
338
|
const weights = json?.value?.weights;
|
|
266
339
|
return { receivers, weights };
|
|
267
340
|
}
|
|
341
|
+
/** A reserve's confidence multiplier `k`, in bps; pass `parsedReserve.config.additionalFields.id`. 0 when unset. */
|
|
342
|
+
static async getReserveConfidenceMultiplierBps(suiGrpcClient, additionalFieldsBagId) {
|
|
343
|
+
const match = await findBagEntryByTypeSuffix(suiGrpcClient, additionalFieldsBagId, "::reserve_config::ConfidenceMultiplierKey");
|
|
344
|
+
if (!match)
|
|
345
|
+
return new BigNumber(0);
|
|
346
|
+
const { dynamicField } = await suiGrpcClient.getDynamicField({
|
|
347
|
+
parentId: additionalFieldsBagId,
|
|
348
|
+
name: match.name,
|
|
349
|
+
});
|
|
350
|
+
return new BigNumber(bcs.u64().parse(dynamicField.value.bcs));
|
|
351
|
+
}
|
|
268
352
|
static createNewLendingMarket(registryId, lendingMarketType, transaction) {
|
|
269
353
|
const [ownerCap, lendingMarket] = createLendingMarket(transaction, lendingMarketType, transaction.object(registryId));
|
|
270
354
|
transaction.moveCall({
|
|
@@ -429,8 +513,7 @@ export class SuilendClient {
|
|
|
429
513
|
const arrayIndex = this.lendingMarket.reserves.findIndex((r) => normalizeStructTag(r.coinType.name) === normalizeStructTag(coinType));
|
|
430
514
|
return BigInt(arrayIndex);
|
|
431
515
|
}
|
|
432
|
-
|
|
433
|
-
const [config] = createReserveConfig(transaction, createReserveConfigArgs);
|
|
516
|
+
submitReserveConfigUpdate(lendingMarketOwnerCapId, transaction, coinType, config) {
|
|
434
517
|
return updateReserveConfig(transaction, [this.lendingMarket.$typeArgs[0], coinType], {
|
|
435
518
|
lendingMarketOwnerCap: transaction.object(lendingMarketOwnerCapId),
|
|
436
519
|
lendingMarket: transaction.object(this.lendingMarket.id),
|
|
@@ -438,6 +521,28 @@ export class SuilendClient {
|
|
|
438
521
|
config: transaction.object(config),
|
|
439
522
|
});
|
|
440
523
|
}
|
|
524
|
+
updateReserveConfig(lendingMarketOwnerCapId, transaction, coinType, createReserveConfigArgs) {
|
|
525
|
+
const [config] = createReserveConfig(transaction, createReserveConfigArgs);
|
|
526
|
+
return this.submitReserveConfigUpdate(lendingMarketOwnerCapId, transaction, coinType, config);
|
|
527
|
+
}
|
|
528
|
+
/**
|
|
529
|
+
* Updates a subset of a reserve's config fields, leaving every unspecified
|
|
530
|
+
* field -- including `additionalFields` entries like the confidence
|
|
531
|
+
* multiplier -- unchanged from the reserve's current on-chain value.
|
|
532
|
+
*/
|
|
533
|
+
patchReserveConfig(lendingMarketOwnerCapId, transaction, coinType, overrides) {
|
|
534
|
+
const [builder] = reserveConfigBuilder(transaction, [this.lendingMarket.$typeArgs[0], coinType], transaction.object(this.lendingMarket.id));
|
|
535
|
+
for (const key of Object.keys(CREATE_RESERVE_CONFIG_FIELD_SETTERS)) {
|
|
536
|
+
applyCreateReserveConfigFieldOverride(transaction, builder, overrides, key);
|
|
537
|
+
}
|
|
538
|
+
if (overrides.confidenceMultiplierBps !== undefined)
|
|
539
|
+
setConfidenceMultiplierBuilder(transaction, {
|
|
540
|
+
builder,
|
|
541
|
+
confidenceMultiplierBps: overrides.confidenceMultiplierBps,
|
|
542
|
+
});
|
|
543
|
+
const [config] = buildReserveConfig(transaction, builder);
|
|
544
|
+
return this.submitReserveConfigUpdate(lendingMarketOwnerCapId, transaction, coinType, config);
|
|
545
|
+
}
|
|
441
546
|
newObligationOwnerCap(transaction, lendingMarketOwnerCapId, destinationAddress, obligationId) {
|
|
442
547
|
const [obligationOwnerCap] = newObligationOwnerCap(transaction, this.lendingMarket.$typeArgs[0], {
|
|
443
548
|
lendingMarketOwnerCap: transaction.object(lendingMarketOwnerCapId),
|
|
@@ -475,7 +580,7 @@ export class SuilendClient {
|
|
|
475
580
|
typeArguments: this.lendingMarket.$typeArgs,
|
|
476
581
|
});
|
|
477
582
|
}
|
|
478
|
-
async refreshAll(transaction, obligation, coinTypes) {
|
|
583
|
+
async refreshAll(transaction, obligation, coinTypes, priceUpdate) {
|
|
479
584
|
const reserveArrayIndexToPriceId = new Map();
|
|
480
585
|
if (obligation) {
|
|
481
586
|
obligation.deposits.forEach((deposit) => {
|
|
@@ -505,8 +610,14 @@ export class SuilendClient {
|
|
|
505
610
|
const priceInfoObjectId = await this.pythClient.getPriceFeedObjectId(priceIdentifiers[i]);
|
|
506
611
|
priceInfoObjectIds[i] = priceInfoObjectId;
|
|
507
612
|
})()));
|
|
508
|
-
const
|
|
509
|
-
|
|
613
|
+
const suppliedPriceIdentifiers = new Set(priceUpdate?.priceIds.map(normalizePriceIdentifier));
|
|
614
|
+
if (priceUpdate?.priceIds.length) {
|
|
615
|
+
await this.pythClient.updatePriceFeeds(transaction, priceUpdate.updateData, priceUpdate.priceIds);
|
|
616
|
+
}
|
|
617
|
+
const stalePriceIdentifiers = new Set();
|
|
618
|
+
await Promise.all(Object.entries(priceInfoObjectIds)
|
|
619
|
+
.filter(([i]) => !suppliedPriceIdentifiers.has(normalizePriceIdentifier(priceIdentifiers[+i])))
|
|
620
|
+
.map(([i, priceInfoObjectId]) => (async () => {
|
|
510
621
|
// Read the field directly: the generated parser's type check is
|
|
511
622
|
// bound to one Pyth package address and would throw post-cutover.
|
|
512
623
|
const { object } = await this.suiGrpcClient.getObject({
|
|
@@ -516,15 +627,16 @@ export class SuilendClient {
|
|
|
516
627
|
const publishTime = priceInfoPublishTime(object.json, priceInfoObjectId);
|
|
517
628
|
const stalenessSeconds = Date.now() / 1000 - Number(publishTime);
|
|
518
629
|
if (needsPriceRefresh(stalenessSeconds)) {
|
|
519
|
-
stalePriceIdentifiers.
|
|
630
|
+
stalePriceIdentifiers.add(priceIdentifiers[+i]);
|
|
520
631
|
}
|
|
521
632
|
})()));
|
|
522
|
-
if (stalePriceIdentifiers.
|
|
523
|
-
const
|
|
524
|
-
await this.
|
|
633
|
+
if (stalePriceIdentifiers.size > 0) {
|
|
634
|
+
const staleIds = [...stalePriceIdentifiers];
|
|
635
|
+
const stalePriceUpdateData = await this.pythConnection.getPriceFeedsUpdateData(staleIds);
|
|
636
|
+
await this.pythClient.updatePriceFeeds(transaction, stalePriceUpdateData, staleIds);
|
|
525
637
|
}
|
|
526
638
|
for (let i = 0; i < reserveArrayIndexes.length; i++) {
|
|
527
|
-
this.refreshReservePrices(transaction, priceInfoObjectIds[i], reserveArrayIndexes[i]);
|
|
639
|
+
await this.refreshReservePrices(transaction, priceInfoObjectIds[i], reserveArrayIndexes[i]);
|
|
528
640
|
}
|
|
529
641
|
}
|
|
530
642
|
async refreshReservePrices(transaction, priceInfoObjectId, reserveArrayIndex) {
|
|
@@ -670,8 +782,8 @@ export class SuilendClient {
|
|
|
670
782
|
transaction.transferObjects([sendCoin], transaction.pure.address(ownerId));
|
|
671
783
|
return result;
|
|
672
784
|
}
|
|
673
|
-
async liquidateAndRedeem(transaction, obligation, repayCoinType, withdrawCoinType, repayCoinId) {
|
|
674
|
-
const [ctokens, exemption] = await this.liquidate(transaction, obligation, repayCoinType, withdrawCoinType, repayCoinId);
|
|
785
|
+
async liquidateAndRedeem(transaction, obligation, repayCoinType, withdrawCoinType, repayCoinId, priceUpdate) {
|
|
786
|
+
const [ctokens, exemption] = await this.liquidate(transaction, obligation, repayCoinType, withdrawCoinType, repayCoinId, priceUpdate);
|
|
675
787
|
const [optionalExemption] = transaction.moveCall({
|
|
676
788
|
target: `0x1::option::some`,
|
|
677
789
|
typeArguments: [
|
|
@@ -681,8 +793,8 @@ export class SuilendClient {
|
|
|
681
793
|
});
|
|
682
794
|
return this.redeem(ctokens, withdrawCoinType, optionalExemption, transaction);
|
|
683
795
|
}
|
|
684
|
-
async liquidate(transaction, obligation, repayCoinType, withdrawCoinType, repayCoinId) {
|
|
685
|
-
await this.refreshAll(transaction, obligation);
|
|
796
|
+
async liquidate(transaction, obligation, repayCoinType, withdrawCoinType, repayCoinId, priceUpdate) {
|
|
797
|
+
await this.refreshAll(transaction, obligation, undefined, priceUpdate);
|
|
686
798
|
return liquidate(transaction, [this.lendingMarket.$typeArgs[0], repayCoinType, withdrawCoinType], {
|
|
687
799
|
lendingMarket: transaction.object(this.lendingMarket.id),
|
|
688
800
|
obligationId: obligation.id,
|
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.
|
|
1
|
+
{"name":"@suilend/sdk","version":"12.4.0-next.gd4b87be","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
|
@@ -12,6 +12,7 @@ import { StrategyType } from "./lib/strategyOwnerCap";
|
|
|
12
12
|
import { ParsedObligation, ParsedReserve } from "./parsers";
|
|
13
13
|
export declare const STRATEGY_E: number;
|
|
14
14
|
export declare const STRATEGY_FLASH_REPAY_MAX_RESIDUAL_USD = 0.1;
|
|
15
|
+
export declare const STRATEGY_RATE_LIMITED_ERROR_MESSAGE = "This withdrawal exceeds the lending market's outflow limit for now. Withdraw a smaller amount, or wait and try again.";
|
|
15
16
|
/**
|
|
16
17
|
* Below this, a levered position is uneconomic no matter what the slippage
|
|
17
18
|
* tolerance says. The aggregator's dust regime dominates: routes go multi-hop
|
|
@@ -73,6 +74,7 @@ export declare const getStrategySuiReserve: (reserveMap: Record<string, ParsedRe
|
|
|
73
74
|
isolated: boolean;
|
|
74
75
|
openAttributedBorrowLimitUsd: number;
|
|
75
76
|
closeAttributedBorrowLimitUsd: number;
|
|
77
|
+
additionalFields: import("./_generated/_dependencies/source/0x2/bag/structs").Bag;
|
|
76
78
|
interestRate: {
|
|
77
79
|
id: string;
|
|
78
80
|
utilPercent: BigNumber;
|
|
@@ -202,6 +204,7 @@ export declare const getStrategyDefaultCurrencyReserve: (reserveMap: Record<stri
|
|
|
202
204
|
isolated: boolean;
|
|
203
205
|
openAttributedBorrowLimitUsd: number;
|
|
204
206
|
closeAttributedBorrowLimitUsd: number;
|
|
207
|
+
additionalFields: import("./_generated/_dependencies/source/0x2/bag/structs").Bag;
|
|
205
208
|
interestRate: {
|
|
206
209
|
id: string;
|
|
207
210
|
utilPercent: BigNumber;
|
|
@@ -297,7 +300,8 @@ export declare const getStrategyBorrowedAmount: (reserveMap: Record<string, Pars
|
|
|
297
300
|
export declare const getStrategyTvlAmount: (reserveMap: Record<string, ParsedReserve>, lstMap: StrategyLstMap, strategyType: StrategyType, obligation?: ParsedObligation) => BigNumber;
|
|
298
301
|
export declare const getStrategyExposure: (reserveMap: Record<string, ParsedReserve>, lstMap: StrategyLstMap, strategyType: StrategyType, obligation?: ParsedObligation) => BigNumber;
|
|
299
302
|
export declare const getStrategyStepMaxBorrowedAmount: (reserveMap: Record<string, ParsedReserve>, lstMap: StrategyLstMap, strategyType: StrategyType, deposits: StrategyDeposit[], borrowedAmount: BigNumber) => BigNumber;
|
|
300
|
-
export declare const getStrategyStepMaxWithdrawnAmount: (reserveMap: Record<string, ParsedReserve>, lstMap: StrategyLstMap, strategyType: StrategyType, deposits: StrategyDeposit[], borrowedAmount: BigNumber, withdrawCoinType: string) => BigNumber;
|
|
303
|
+
export declare const getStrategyStepMaxWithdrawnAmount: (reserveMap: Record<string, ParsedReserve>, lstMap: StrategyLstMap, strategyType: StrategyType, deposits: StrategyDeposit[], borrowedAmount: BigNumber, withdrawCoinType: string, headroomBuffer?: number) => BigNumber;
|
|
304
|
+
export declare const getStrategyRebalanceDepositAmount: (reserveMap: Record<string, ParsedReserve>, lstMap: StrategyLstMap, strategyType: StrategyType, deposits: StrategyDeposit[], borrowedAmount: BigNumber) => BigNumber;
|
|
301
305
|
export declare const strategySimulateLoopToExposure: (reserveMap: Record<string, ParsedReserve>, lstMap: StrategyLstMap, strategyType: StrategyType, _deposits: StrategyDeposit[], _borrowedAmount: BigNumber, _targetBorrowedAmount: BigNumber | undefined, _targetExposure: BigNumber | undefined) => {
|
|
302
306
|
deposits: StrategyDeposit[];
|
|
303
307
|
borrowedAmount: BigNumber;
|
|
@@ -357,7 +361,7 @@ export declare const strategyWithdrawTx: (reserveMap: Record<string, ParsedReser
|
|
|
357
361
|
transaction: Transaction;
|
|
358
362
|
withdrawnCoin?: TransactionArgument;
|
|
359
363
|
}>;
|
|
360
|
-
export declare const strategyMaxWithdrawTx: (reserveMap: Record<string, ParsedReserve>, rewardsMap: RewardsMap, lstMap: StrategyLstMap, strategyType: StrategyType, suiGrpcClient: SuiGrpcClient, suilendClient: SuilendClient, cetusSdk: CetusSdk, cetusPartnerId: string, _address: string, strategyOwnerCapId: TransactionObjectInput, obligationId: string, _deposits: StrategyDeposit[], _borrowedAmount: BigNumber, withdrawCoinType: string, transaction: Transaction,
|
|
364
|
+
export declare const strategyMaxWithdrawTx: (reserveMap: Record<string, ParsedReserve>, rewardsMap: RewardsMap, lstMap: StrategyLstMap, strategyType: StrategyType, suiGrpcClient: SuiGrpcClient, suilendClient: SuilendClient, cetusSdk: CetusSdk, cetusPartnerId: string, _address: string, strategyOwnerCapId: TransactionObjectInput, obligationId: string, _deposits: StrategyDeposit[], _borrowedAmount: BigNumber, withdrawCoinType: string, transaction: Transaction, _dryRunTransaction: (transaction: Transaction, setGasBudget?: boolean) => Promise<SuiClientTypes.SimulateTransactionResult<{
|
|
361
365
|
commandResults: true;
|
|
362
366
|
}>>) => Promise<{
|
|
363
367
|
deposits: StrategyDeposit[];
|
|
@@ -378,7 +382,7 @@ export declare const strategyDepositAdjustWithdrawTx: (reserveMap: Record<string
|
|
|
378
382
|
borrowedAmount: BigNumber;
|
|
379
383
|
transaction: Transaction;
|
|
380
384
|
}>;
|
|
381
|
-
export declare const strategyAdjustTx: (reserveMap: Record<string, ParsedReserve>, lstMap: StrategyLstMap, strategyType: StrategyType, suiGrpcClient: SuiGrpcClient, suilendClient: SuilendClient, cetusSdk: CetusSdk, cetusPartnerId: string, _address: string, strategyOwnerCapId: TransactionObjectInput, obligation: ParsedObligation, _deposits: StrategyDeposit[], _borrowedAmount: BigNumber, targetExposure: BigNumber, transaction: Transaction,
|
|
385
|
+
export declare const strategyAdjustTx: (reserveMap: Record<string, ParsedReserve>, lstMap: StrategyLstMap, strategyType: StrategyType, suiGrpcClient: SuiGrpcClient, suilendClient: SuilendClient, cetusSdk: CetusSdk, cetusPartnerId: string, _address: string, strategyOwnerCapId: TransactionObjectInput, obligation: ParsedObligation, _deposits: StrategyDeposit[], _borrowedAmount: BigNumber, targetExposure: BigNumber, transaction: Transaction, _dryRunTransaction: (transaction: Transaction, setGasBudget?: boolean) => Promise<SuiClientTypes.SimulateTransactionResult<{
|
|
382
386
|
commandResults: true;
|
|
383
387
|
}>>) => Promise<{
|
|
384
388
|
deposits: StrategyDeposit[];
|
package/strategies.js
CHANGED
|
@@ -10,9 +10,28 @@ import { getNetAprPercent } from "./lib/index.js";
|
|
|
10
10
|
import { getRewardsMap } from "./lib/liquidityMining.js";
|
|
11
11
|
import { STRATEGY_TYPE_INFO_MAP, StrategyType, strategyBorrow, strategyClaimRewardsAndMergeCoins, strategyDeposit, strategyWithdraw, } from "./lib/strategyOwnerCap.js";
|
|
12
12
|
import { MMT_CONTRACT_PACKAGE_ID, MMT_VERSION_OBJECT_ID } from "./mmt.js";
|
|
13
|
-
import { getWeightedBorrowsUsd } from "./utils/index.js";
|
|
13
|
+
import { bisectionMethod, getWeightedBorrowsUsd } from "./utils/index.js";
|
|
14
14
|
export const STRATEGY_E = 10 ** -7;
|
|
15
15
|
export const STRATEGY_FLASH_REPAY_MAX_RESIDUAL_USD = 0.1;
|
|
16
|
+
export const STRATEGY_RATE_LIMITED_ERROR_MESSAGE = "This withdrawal exceeds the lending market's outflow limit for now. Withdraw a smaller amount, or wait and try again.";
|
|
17
|
+
const isRateLimitExceeded = (err) => /abort code: 2, in '[^']*::rate_limiter::process_qty'/.test(err instanceof Error ? err.message : String(err));
|
|
18
|
+
// Every step dry-runs and rolls back on failure, so the builders only see that
|
|
19
|
+
// no step landed. Recording why lets them name the market's outflow limit
|
|
20
|
+
// instead of reporting a generic failure.
|
|
21
|
+
const trackRateLimit = (dryRunTransaction) => {
|
|
22
|
+
const state = { exceeded: false };
|
|
23
|
+
const tracked = (async (...args) => {
|
|
24
|
+
try {
|
|
25
|
+
return await dryRunTransaction(...args);
|
|
26
|
+
}
|
|
27
|
+
catch (err) {
|
|
28
|
+
if (isRateLimitExceeded(err))
|
|
29
|
+
state.exceeded = true;
|
|
30
|
+
throw err;
|
|
31
|
+
}
|
|
32
|
+
});
|
|
33
|
+
return { rateLimit: state, dryRunTransaction: tracked };
|
|
34
|
+
};
|
|
16
35
|
/**
|
|
17
36
|
* Cetus applies its slippage check against its OWN quote, so a wrong quote yields
|
|
18
37
|
* a wrong floor. Measured on a mainnet unwind of a sub-dollar position: the
|
|
@@ -591,7 +610,7 @@ export const getStrategyStepMaxWithdrawnAmount = (
|
|
|
591
610
|
// AppContext
|
|
592
611
|
reserveMap,
|
|
593
612
|
// Strategy
|
|
594
|
-
lstMap, strategyType, deposits, borrowedAmount, withdrawCoinType) => {
|
|
613
|
+
lstMap, strategyType, deposits, borrowedAmount, withdrawCoinType, headroomBuffer = 1) => {
|
|
595
614
|
const depositReserves = getStrategyDepositReserves(reserveMap, strategyType);
|
|
596
615
|
const borrowReserve = getStrategyBorrowReserve(reserveMap, strategyType);
|
|
597
616
|
const defaultCurrencyReserve = getStrategyDefaultCurrencyReserve(reserveMap, strategyType);
|
|
@@ -608,10 +627,45 @@ lstMap, strategyType, deposits, borrowedAmount, withdrawCoinType) => {
|
|
|
608
627
|
.minus(obligation.maxPriceWeightedBorrowsUsd)
|
|
609
628
|
.div(withdrawReserve.minPrice)
|
|
610
629
|
.div(withdrawReserve.config.openLtvPct / 100)
|
|
630
|
+
.decimalPlaces(withdrawReserve.token.decimals, BigNumber.ROUND_DOWN)
|
|
631
|
+
.times(headroomBuffer)
|
|
611
632
|
: MAX_U64, // Infinity
|
|
612
633
|
deposits.find((deposit) => deposit.coinType === withdrawReserve.coinType)
|
|
613
634
|
?.depositedAmount ?? new BigNumber(0)).decimalPlaces(withdrawReserve.token.decimals, BigNumber.ROUND_DOWN);
|
|
614
635
|
};
|
|
636
|
+
export const getStrategyRebalanceDepositAmount = (
|
|
637
|
+
// AppContext
|
|
638
|
+
reserveMap,
|
|
639
|
+
// Strategy
|
|
640
|
+
lstMap, strategyType, deposits, borrowedAmount) => {
|
|
641
|
+
const depositReserves = getStrategyDepositReserves(reserveMap, strategyType);
|
|
642
|
+
const borrowReserve = getStrategyBorrowReserve(reserveMap, strategyType);
|
|
643
|
+
const depositReserve = (depositReserves.base ?? depositReserves.lst); // Must have LST if no base
|
|
644
|
+
const simulateWith = (additionalAmount) => getStrategySimulatedObligation(reserveMap, lstMap, strategyType, addOrInsertStrategyDeposit(deposits, {
|
|
645
|
+
coinType: depositReserve.coinType,
|
|
646
|
+
depositedAmount: additionalAmount,
|
|
647
|
+
}), borrowedAmount);
|
|
648
|
+
const isRebalanced = (additionalAmount) => {
|
|
649
|
+
const obligation = simulateWith(additionalAmount);
|
|
650
|
+
return (getStrategyHealthPercent(reserveMap, lstMap, strategyType, obligation).eq(100) &&
|
|
651
|
+
getStrategyExposure(reserveMap, lstMap, strategyType, obligation).lte(STRATEGY_TYPE_EXPOSURE_MAP[strategyType].max));
|
|
652
|
+
};
|
|
653
|
+
if (isRebalanced(new BigNumber(0)))
|
|
654
|
+
return new BigNumber(0);
|
|
655
|
+
// Both conditions only get easier as base is added, so a doubled bound
|
|
656
|
+
// that satisfies them brackets the smallest amount that does.
|
|
657
|
+
let upperBound = BigNumber.max(deposits.find((d) => d.coinType === depositReserve.coinType)
|
|
658
|
+
?.depositedAmount ?? new BigNumber(0), borrowedAmount.times(borrowReserve.price).div(depositReserve.price));
|
|
659
|
+
let upperBoundRebalances = isRebalanced(upperBound);
|
|
660
|
+
for (let i = 0; i < 30 && !upperBoundRebalances; i++) {
|
|
661
|
+
upperBound = upperBound.times(2);
|
|
662
|
+
upperBoundRebalances = isRebalanced(upperBound);
|
|
663
|
+
}
|
|
664
|
+
if (!upperBoundRebalances)
|
|
665
|
+
throw new Error("No deposit brings the position back within its borrow limit");
|
|
666
|
+
const smallest = bisectionMethod(new BigNumber(0), upperBound, isRebalanced);
|
|
667
|
+
return (smallest.gt(0) ? smallest : upperBound).decimalPlaces(depositReserve.token.decimals, BigNumber.ROUND_UP);
|
|
668
|
+
};
|
|
615
669
|
// Simulate
|
|
616
670
|
export const strategySimulateLoopToExposure = (
|
|
617
671
|
// AppContext
|
|
@@ -1236,7 +1290,7 @@ transaction, dryRunTransaction) => {
|
|
|
1236
1290
|
_targetExposure: _targetExposure?.toFixed(20),
|
|
1237
1291
|
}, null, 2));
|
|
1238
1292
|
const depositReserve = (depositReserves.base ?? depositReserves.lst); // Must have LST if no base
|
|
1239
|
-
|
|
1293
|
+
let loopingDepositReserve = (depositReserves.lst ?? depositReserves.base); // Must have base if no LST
|
|
1240
1294
|
//
|
|
1241
1295
|
let deposits = cloneDeep(_deposits);
|
|
1242
1296
|
let borrowedAmount = _borrowedAmount;
|
|
@@ -1426,7 +1480,9 @@ transaction, dryRunTransaction) => {
|
|
|
1426
1480
|
fullRepaymentAmount: fullRepaymentAmount.toFixed(20),
|
|
1427
1481
|
}));
|
|
1428
1482
|
// 1) MAX withdraw LST
|
|
1429
|
-
if (depositReserves.lst !== undefined
|
|
1483
|
+
if (depositReserves.lst !== undefined &&
|
|
1484
|
+
(deposits.find((d) => d.coinType === depositReserves.lst.coinType)
|
|
1485
|
+
?.depositedAmount ?? new BigNumber(0)).gt(0)) {
|
|
1430
1486
|
// 1.1) MAX withdraw
|
|
1431
1487
|
const [withdrawnMaxLstCoin] = strategyWithdraw(strategyType, depositReserves.lst.coinType, strategyOwnerCapId, suilendClient.findReserveArrayIndex(depositReserves.lst.coinType), BigInt(MAX_U64.toString()), transaction);
|
|
1432
1488
|
// 1.2) Update state
|
|
@@ -1585,9 +1641,10 @@ transaction, dryRunTransaction) => {
|
|
|
1585
1641
|
if (pendingBorrowedAmount.lt(0))
|
|
1586
1642
|
break; // Fully repaid already
|
|
1587
1643
|
if (depositReserve.coinType === depositReserves.base?.coinType) {
|
|
1588
|
-
const
|
|
1644
|
+
const lstDepositedAmount = deposits.find((d) => d.coinType === depositReserves.lst.coinType)
|
|
1645
|
+
?.depositedAmount ?? new BigNumber(0);
|
|
1589
1646
|
// Ran out of LST
|
|
1590
|
-
if (
|
|
1647
|
+
if (lstDepositedAmount.lte(STRATEGY_E)) {
|
|
1591
1648
|
// 1. MAX withdraws LST (transferred to user as SUI)
|
|
1592
1649
|
// 2. Withdraws base to cover borrows
|
|
1593
1650
|
// - Leftover transferred to user as borrow coinType, e.g. SUI or suiWBTC
|
|
@@ -1643,12 +1700,17 @@ transaction, dryRunTransaction) => {
|
|
|
1643
1700
|
else {
|
|
1644
1701
|
if (pendingBorrowedAmount.lte(STRATEGY_E))
|
|
1645
1702
|
break;
|
|
1703
|
+
// The rest of the debt is backed by base, so keep unwinding from it.
|
|
1704
|
+
if (depositReserves.base !== undefined &&
|
|
1705
|
+
(deposits.find((d) => d.coinType === depositReserves.lst.coinType)
|
|
1706
|
+
?.depositedAmount ?? new BigNumber(0)).lte(STRATEGY_E)) {
|
|
1707
|
+
loopingDepositReserve = depositReserves.base;
|
|
1708
|
+
continue;
|
|
1709
|
+
}
|
|
1646
1710
|
}
|
|
1647
1711
|
// 1) Withdraw LST
|
|
1648
1712
|
// 1.1) Max
|
|
1649
|
-
const stepMaxWithdrawnAmount = getStrategyStepMaxWithdrawnAmount(reserveMap, lstMap, strategyType, deposits, borrowedAmount, loopingDepositReserve.coinType)
|
|
1650
|
-
.times(0.9) // 10% buffer
|
|
1651
|
-
.decimalPlaces(loopingDepositReserve.token.decimals, BigNumber.ROUND_DOWN);
|
|
1713
|
+
const stepMaxWithdrawnAmount = getStrategyStepMaxWithdrawnAmount(reserveMap, lstMap, strategyType, deposits, borrowedAmount, loopingDepositReserve.coinType, 0.9);
|
|
1652
1714
|
const stepMaxRepaidAmount = new BigNumber(new BigNumber(stepMaxWithdrawnAmount.times(lst?.lstToSuiExchangeRate ?? 1)).minus(getStrategyLstRedeemFee(lstMap, loopingDepositReserve.coinType, stepMaxWithdrawnAmount))).decimalPlaces(borrowReserve.token.decimals, BigNumber.ROUND_DOWN);
|
|
1653
1715
|
console.log(`[unloopStrategyToExposure] ${i} withdraw_lst.max |`, JSON.stringify({
|
|
1654
1716
|
stepMaxWithdrawnAmount: stepMaxWithdrawnAmount.toFixed(20),
|
|
@@ -1785,9 +1847,7 @@ transaction, dryRunTransaction) => {
|
|
|
1785
1847
|
}
|
|
1786
1848
|
// 1) Withdraw base
|
|
1787
1849
|
// 1.1) Max
|
|
1788
|
-
const stepMaxWithdrawnAmount = getStrategyStepMaxWithdrawnAmount(reserveMap, lstMap, strategyType, deposits, borrowedAmount, loopingDepositReserve.coinType)
|
|
1789
|
-
.times(0.9) // 10% buffer
|
|
1790
|
-
.decimalPlaces(loopingDepositReserve.token.decimals, BigNumber.ROUND_DOWN);
|
|
1850
|
+
const stepMaxWithdrawnAmount = getStrategyStepMaxWithdrawnAmount(reserveMap, lstMap, strategyType, deposits, borrowedAmount, loopingDepositReserve.coinType, 0.9);
|
|
1791
1851
|
const stepMaxRepaidAmount = new BigNumber(stepMaxWithdrawnAmount.times(baseToBorrowExchangeRate)).decimalPlaces(borrowReserve.token.decimals, BigNumber.ROUND_DOWN);
|
|
1792
1852
|
console.log(`[unloopStrategyToExposure] ${i} withdraw_base.max |`, JSON.stringify({
|
|
1793
1853
|
stepMaxWithdrawnAmount: stepMaxWithdrawnAmount.toFixed(20),
|
|
@@ -2325,7 +2385,8 @@ export const strategyMaxWithdrawTx = async (
|
|
|
2325
2385
|
// AppContext
|
|
2326
2386
|
reserveMap, rewardsMap,
|
|
2327
2387
|
// Strategy
|
|
2328
|
-
lstMap, strategyType, suiGrpcClient, suilendClient, cetusSdk, cetusPartnerId, _address, strategyOwnerCapId, obligationId, _deposits, _borrowedAmount, withdrawCoinType, transaction,
|
|
2388
|
+
lstMap, strategyType, suiGrpcClient, suilendClient, cetusSdk, cetusPartnerId, _address, strategyOwnerCapId, obligationId, _deposits, _borrowedAmount, withdrawCoinType, transaction, _dryRunTransaction) => {
|
|
2389
|
+
const { rateLimit, dryRunTransaction } = trackRateLimit(_dryRunTransaction);
|
|
2329
2390
|
const strategyInfo = STRATEGY_TYPE_INFO_MAP[strategyType];
|
|
2330
2391
|
const lst = strategyInfo.depositLstCoinType !== undefined
|
|
2331
2392
|
? lstMap[strategyInfo.depositLstCoinType]
|
|
@@ -2351,14 +2412,37 @@ lstMap, strategyType, suiGrpcClient, suilendClient, cetusSdk, cetusPartnerId, _a
|
|
|
2351
2412
|
let borrowedAmount = _borrowedAmount;
|
|
2352
2413
|
// 1) Unloop to 1x (base+LST: no LST and no borrows, LST: no borrows)
|
|
2353
2414
|
if (borrowedAmount.gt(0)) {
|
|
2354
|
-
|
|
2355
|
-
const { deposits: newDeposits, borrowedAmount: newBorrowedAmount, transaction: newTransaction, } = await strategyUnloopToExposureTx(reserveMap, lstMap, strategyType, suiGrpcClient, suilendClient, cetusSdk, cetusPartnerId, _address, strategyOwnerCapId, obligationId, deposits, borrowedAmount, undefined, // Don't pass targetBorrowedAmount
|
|
2415
|
+
const unloopToOne = (fromDeposits, fromBorrowedAmount, fromTransaction) => strategyUnloopToExposureTx(reserveMap, lstMap, strategyType, suiGrpcClient, suilendClient, cetusSdk, cetusPartnerId, _address, strategyOwnerCapId, obligationId, fromDeposits, fromBorrowedAmount, undefined, // Don't pass targetBorrowedAmount
|
|
2356
2416
|
new BigNumber(1), // Pass targetExposure
|
|
2357
|
-
|
|
2417
|
+
fromTransaction, dryRunTransaction);
|
|
2418
|
+
// 1.1) Unloop
|
|
2419
|
+
let unlooped;
|
|
2420
|
+
let plainError;
|
|
2421
|
+
try {
|
|
2422
|
+
unlooped = await unloopToOne(deposits, borrowedAmount, Transaction.from(transaction));
|
|
2423
|
+
}
|
|
2424
|
+
catch (err) {
|
|
2425
|
+
plainError = err;
|
|
2426
|
+
}
|
|
2427
|
+
// 1.1b) Above the borrow limit no stepwise withdraw can land, so rebalance and retry
|
|
2428
|
+
if ((unlooped === undefined || unlooped.borrowedAmount.gt(STRATEGY_E)) &&
|
|
2429
|
+
!isStrategyWithinBorrowLimit(reserveMap, lstMap, strategyType, deposits, borrowedAmount)) {
|
|
2430
|
+
try {
|
|
2431
|
+
const rebalanced = await strategyRebalanceWithinBorrowLimitTx(reserveMap, lstMap, strategyType, suiGrpcClient, suilendClient, cetusSdk, cetusPartnerId, _address, strategyOwnerCapId, obligationId, deposits, borrowedAmount, Transaction.from(transaction), dryRunTransaction);
|
|
2432
|
+
unlooped = await unloopToOne(rebalanced.deposits, rebalanced.borrowedAmount, rebalanced.transaction);
|
|
2433
|
+
}
|
|
2434
|
+
catch (err) {
|
|
2435
|
+
console.error(err);
|
|
2436
|
+
}
|
|
2437
|
+
}
|
|
2438
|
+
if (unlooped === undefined)
|
|
2439
|
+
throw rateLimit.exceeded
|
|
2440
|
+
? new Error(STRATEGY_RATE_LIMITED_ERROR_MESSAGE)
|
|
2441
|
+
: plainError;
|
|
2358
2442
|
// 1.2) Update state
|
|
2359
|
-
deposits =
|
|
2360
|
-
borrowedAmount =
|
|
2361
|
-
transaction =
|
|
2443
|
+
deposits = unlooped.deposits;
|
|
2444
|
+
borrowedAmount = unlooped.borrowedAmount;
|
|
2445
|
+
transaction = unlooped.transaction;
|
|
2362
2446
|
console.log(`[strategyMaxWithdraw] unloop.update_state |`, JSON.stringify({
|
|
2363
2447
|
deposits: deposits.map((d) => ({
|
|
2364
2448
|
coinType: d.coinType,
|
|
@@ -2373,6 +2457,32 @@ lstMap, strategyType, suiGrpcClient, suilendClient, cetusSdk, cetusPartnerId, _a
|
|
|
2373
2457
|
// rather than build a transaction that is certain to abort on the health check —
|
|
2374
2458
|
// the caller can surface this instead of asking the user to sign it.
|
|
2375
2459
|
let flashLoan;
|
|
2460
|
+
let repaidByAdjustRepay = false;
|
|
2461
|
+
if (borrowedAmount.gt(STRATEGY_E) &&
|
|
2462
|
+
depositReserves.base !== undefined &&
|
|
2463
|
+
depositReserves.lst !== undefined) {
|
|
2464
|
+
const baseFlashLoanAmount = borrowedAmount
|
|
2465
|
+
.times(1.01)
|
|
2466
|
+
.decimalPlaces(borrowReserve.token.decimals, BigNumber.ROUND_UP)
|
|
2467
|
+
.times(borrowReserve.price)
|
|
2468
|
+
.div(depositReserves.base.price)
|
|
2469
|
+
.times(1.03)
|
|
2470
|
+
.decimalPlaces(depositReserves.base.token.decimals, BigNumber.ROUND_UP);
|
|
2471
|
+
console.log(`[strategyMaxWithdraw] adjust_repay |`, JSON.stringify({
|
|
2472
|
+
borrowedAmount: borrowedAmount.toFixed(20),
|
|
2473
|
+
baseFlashLoanAmount: baseFlashLoanAmount.toFixed(20),
|
|
2474
|
+
}));
|
|
2475
|
+
try {
|
|
2476
|
+
({ deposits, borrowedAmount, transaction } = await strategyAdjustRepayTx(reserveMap, lstMap, strategyType, suiGrpcClient, suilendClient, cetusSdk, cetusPartnerId, _address, strategyOwnerCapId, obligationId, deposits, borrowedAmount, baseFlashLoanAmount, Transaction.from(transaction), dryRunTransaction));
|
|
2477
|
+
}
|
|
2478
|
+
catch (err) {
|
|
2479
|
+
console.error(err);
|
|
2480
|
+
throw new Error(rateLimit.exceeded
|
|
2481
|
+
? STRATEGY_RATE_LIMITED_ERROR_MESSAGE
|
|
2482
|
+
: "Failed to repay borrows before withdrawing");
|
|
2483
|
+
}
|
|
2484
|
+
repaidByAdjustRepay = true;
|
|
2485
|
+
}
|
|
2376
2486
|
if (borrowedAmount.gt(STRATEGY_E)) {
|
|
2377
2487
|
const flashLoanObj = STRATEGY_TYPE_FLASH_LOAN_OBJ_MAP[strategyType];
|
|
2378
2488
|
if (flashLoanObj.provider !== StrategyFlashLoanProvider.MMT ||
|
|
@@ -2385,7 +2495,9 @@ lstMap, strategyType, suiGrpcClient, suilendClient, cetusSdk, cetusPartnerId, _a
|
|
|
2385
2495
|
borrowedAmount
|
|
2386
2496
|
.times(borrowReserve.price)
|
|
2387
2497
|
.gte(STRATEGY_FLASH_REPAY_MAX_RESIDUAL_USD))
|
|
2388
|
-
throw new Error(
|
|
2498
|
+
throw new Error(rateLimit.exceeded
|
|
2499
|
+
? STRATEGY_RATE_LIMITED_ERROR_MESSAGE
|
|
2500
|
+
: "Failed to repay borrows before withdrawing");
|
|
2389
2501
|
const flashLoanBorrowedAmount = borrowedAmount
|
|
2390
2502
|
.times(1.1)
|
|
2391
2503
|
.decimalPlaces(borrowReserve.token.decimals, BigNumber.ROUND_UP);
|
|
@@ -2525,13 +2637,15 @@ lstMap, strategyType, suiGrpcClient, suilendClient, cetusSdk, cetusPartnerId, _a
|
|
|
2525
2637
|
transaction = transactionBeforeClaim;
|
|
2526
2638
|
}
|
|
2527
2639
|
}
|
|
2528
|
-
if (flashLoan !== undefined || borrowedAmount.gt(0)) {
|
|
2640
|
+
if (flashLoan !== undefined || repaidByAdjustRepay || borrowedAmount.gt(0)) {
|
|
2529
2641
|
try {
|
|
2530
2642
|
await dryRunTransaction(transaction);
|
|
2531
2643
|
}
|
|
2532
2644
|
catch (err) {
|
|
2533
2645
|
console.error(err);
|
|
2534
|
-
throw new Error(
|
|
2646
|
+
throw new Error(rateLimit.exceeded
|
|
2647
|
+
? STRATEGY_RATE_LIMITED_ERROR_MESSAGE
|
|
2648
|
+
: "Failed to repay borrows before withdrawing");
|
|
2535
2649
|
}
|
|
2536
2650
|
}
|
|
2537
2651
|
return { deposits, borrowedAmount, transaction };
|
|
@@ -2971,11 +3085,14 @@ lstMap, strategyType, suiGrpcClient, suilendClient, cetusSdk, cetusPartnerId, _a
|
|
|
2971
3085
|
}
|
|
2972
3086
|
return { deposits, borrowedAmount, transaction };
|
|
2973
3087
|
};
|
|
3088
|
+
const isStrategyWithinBorrowLimit = (reserveMap, lstMap, strategyType, deposits, borrowedAmount) => getStrategyHealthPercent(reserveMap, lstMap, strategyType, getStrategySimulatedObligation(reserveMap, lstMap, strategyType, deposits, borrowedAmount)).eq(100);
|
|
3089
|
+
const strategyRebalanceWithinBorrowLimitTx = (reserveMap, lstMap, strategyType, suiGrpcClient, suilendClient, cetusSdk, cetusPartnerId, _address, strategyOwnerCapId, obligationId, deposits, borrowedAmount, transaction, dryRunTransaction) => strategyDepositAdjustWithdrawTx(reserveMap, lstMap, strategyType, suiGrpcClient, suilendClient, cetusSdk, cetusPartnerId, _address, strategyOwnerCapId, obligationId, deposits, borrowedAmount, getStrategyRebalanceDepositAmount(reserveMap, lstMap, strategyType, deposits, borrowedAmount), transaction, dryRunTransaction);
|
|
2974
3090
|
export const strategyAdjustTx = async (
|
|
2975
3091
|
// AppContext
|
|
2976
3092
|
reserveMap,
|
|
2977
3093
|
// Strategy
|
|
2978
|
-
lstMap, strategyType, suiGrpcClient, suilendClient, cetusSdk, cetusPartnerId, _address, strategyOwnerCapId, obligation, _deposits, _borrowedAmount, targetExposure, transaction,
|
|
3094
|
+
lstMap, strategyType, suiGrpcClient, suilendClient, cetusSdk, cetusPartnerId, _address, strategyOwnerCapId, obligation, _deposits, _borrowedAmount, targetExposure, transaction, _dryRunTransaction) => {
|
|
3095
|
+
const { rateLimit, dryRunTransaction } = trackRateLimit(_dryRunTransaction);
|
|
2979
3096
|
const strategyInfo = STRATEGY_TYPE_INFO_MAP[strategyType];
|
|
2980
3097
|
const lst = strategyInfo.depositLstCoinType !== undefined
|
|
2981
3098
|
? lstMap[strategyInfo.depositLstCoinType]
|
|
@@ -2998,15 +3115,54 @@ lstMap, strategyType, suiGrpcClient, suilendClient, cetusSdk, cetusPartnerId, _a
|
|
|
2998
3115
|
//
|
|
2999
3116
|
const deposits = cloneDeep(_deposits);
|
|
3000
3117
|
const borrowedAmount = _borrowedAmount;
|
|
3001
|
-
|
|
3002
|
-
|
|
3003
|
-
|
|
3118
|
+
if (targetExposure.eq(exposure))
|
|
3119
|
+
return { deposits, borrowedAmount, transaction };
|
|
3120
|
+
const loopOrUnloop = (fromDeposits, fromBorrowedAmount, fromExposure, fromTransaction) => targetExposure.gt(fromExposure)
|
|
3121
|
+
? strategyLoopToExposureTx(reserveMap, lstMap, strategyType, suiGrpcClient, suilendClient, cetusSdk, cetusPartnerId, _address, strategyOwnerCapId, obligation.id, fromDeposits, fromBorrowedAmount, undefined, // Don't pass targetBorrowedAmount
|
|
3004
3122
|
targetExposure, // Pass targetExposure
|
|
3005
|
-
|
|
3006
|
-
|
|
3007
|
-
return strategyUnloopToExposureTx(reserveMap, lstMap, strategyType, suiGrpcClient, suilendClient, cetusSdk, cetusPartnerId, _address, strategyOwnerCapId, obligation.id, deposits, borrowedAmount, undefined, // Don't pass targetBorrowedAmount
|
|
3123
|
+
fromTransaction)
|
|
3124
|
+
: strategyUnloopToExposureTx(reserveMap, lstMap, strategyType, suiGrpcClient, suilendClient, cetusSdk, cetusPartnerId, _address, strategyOwnerCapId, obligation.id, fromDeposits, fromBorrowedAmount, undefined, // Don't pass targetBorrowedAmount
|
|
3008
3125
|
targetExposure, // Pass targetExposure
|
|
3009
|
-
|
|
3010
|
-
|
|
3011
|
-
|
|
3126
|
+
fromTransaction, dryRunTransaction);
|
|
3127
|
+
// 1) Loop or unloop to target exposure
|
|
3128
|
+
let plainError;
|
|
3129
|
+
try {
|
|
3130
|
+
const result = await loopOrUnloop(deposits, borrowedAmount, exposure, Transaction.from(transaction));
|
|
3131
|
+
if (!result.borrowedAmount.eq(borrowedAmount))
|
|
3132
|
+
return result;
|
|
3133
|
+
}
|
|
3134
|
+
catch (err) {
|
|
3135
|
+
plainError = err;
|
|
3136
|
+
}
|
|
3137
|
+
// 2) Above the borrow limit no stepwise withdraw can land, so rebalance and retry
|
|
3138
|
+
if (borrowedAmount.gt(0) &&
|
|
3139
|
+
!isStrategyWithinBorrowLimit(reserveMap, lstMap, strategyType, deposits, borrowedAmount)) {
|
|
3140
|
+
let rebalanced;
|
|
3141
|
+
try {
|
|
3142
|
+
rebalanced = await strategyRebalanceWithinBorrowLimitTx(reserveMap, lstMap, strategyType, suiGrpcClient, suilendClient, cetusSdk, cetusPartnerId, _address, strategyOwnerCapId, obligation.id, deposits, borrowedAmount, Transaction.from(transaction), dryRunTransaction);
|
|
3143
|
+
}
|
|
3144
|
+
catch (err) {
|
|
3145
|
+
console.error(err);
|
|
3146
|
+
}
|
|
3147
|
+
if (rebalanced !== undefined) {
|
|
3148
|
+
const rebalancedExposure = getStrategyExposure(reserveMap, lstMap, strategyType, getStrategySimulatedObligation(reserveMap, lstMap, strategyType, rebalanced.deposits, rebalanced.borrowedAmount));
|
|
3149
|
+
if (targetExposure.eq(rebalancedExposure))
|
|
3150
|
+
return rebalanced;
|
|
3151
|
+
try {
|
|
3152
|
+
const result = await loopOrUnloop(rebalanced.deposits, rebalanced.borrowedAmount, rebalancedExposure, Transaction.from(rebalanced.transaction));
|
|
3153
|
+
return result.borrowedAmount.eq(rebalanced.borrowedAmount)
|
|
3154
|
+
? rebalanced
|
|
3155
|
+
: result;
|
|
3156
|
+
}
|
|
3157
|
+
catch (err) {
|
|
3158
|
+
console.error(err);
|
|
3159
|
+
return rebalanced;
|
|
3160
|
+
}
|
|
3161
|
+
}
|
|
3162
|
+
}
|
|
3163
|
+
if (rateLimit.exceeded)
|
|
3164
|
+
throw new Error(STRATEGY_RATE_LIMITED_ERROR_MESSAGE);
|
|
3165
|
+
if (plainError !== undefined)
|
|
3166
|
+
throw plainError;
|
|
3167
|
+
throw new Error("Failed to adjust leverage");
|
|
3012
3168
|
};
|
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
|
+
}
|