@suilend/sdk 12.0.0 → 12.1.0
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/lib/initialize.d.ts +274 -3
- package/lib/initialize.js +147 -121
- package/package.json +1 -1
- package/utils/simulate.d.ts +7 -0
- package/utils/simulate.js +27 -25
package/lib/initialize.d.ts
CHANGED
|
@@ -20,6 +20,10 @@ export declare const resolveCoinMetadataForInitialize: (miscCoinTypes: string[],
|
|
|
20
20
|
reserveCoinMetadataMap: Record<string, import("@suilend/sui-core").Token>;
|
|
21
21
|
rewardCoinMetadataMap: Record<string, import("@suilend/sui-core").Token>;
|
|
22
22
|
}>;
|
|
23
|
+
export declare const refreshReservesForInitializeAll: (reservesByMarket: Reserve<string>[][], pythConnection: simulate.PriceFeedSource, tolerateMissingPriceFeeds?: boolean) => Promise<{
|
|
24
|
+
reserves: Reserve<string>[];
|
|
25
|
+
unpricedCoinTypes: string[];
|
|
26
|
+
}[]>;
|
|
23
27
|
/**
|
|
24
28
|
* Price reserves for `initializeSuilend`, honouring the opt-in tolerance flag.
|
|
25
29
|
*
|
|
@@ -47,7 +51,8 @@ export declare const refreshReservesForInitialize: (reserves: Reserve<string>[],
|
|
|
47
51
|
* leave unverified.
|
|
48
52
|
*/
|
|
49
53
|
export declare const priceTemporaryPythFeedReserves: (reserves: Reserve<string>[], fetchPrices?: (coinTypes: string[]) => Promise<Record<string, number | undefined>>) => Promise<string[]>;
|
|
50
|
-
export declare const
|
|
54
|
+
export declare const priceTemporaryPythFeedReservesAll: (reservesByMarket: Reserve<string>[][], fetchPrices?: (coinTypes: string[]) => Promise<Record<string, number | undefined>>) => Promise<string[][]>;
|
|
55
|
+
export type InitializeSuilendOptions = {
|
|
51
56
|
/**
|
|
52
57
|
* Price connection to use instead of the one this function would build.
|
|
53
58
|
*
|
|
@@ -127,7 +132,267 @@ export declare const initializeSuilend: (suiGrpcClient: SuiGrpcClient, suilendCl
|
|
|
127
132
|
* prices them at literal zero.
|
|
128
133
|
*/
|
|
129
134
|
tolerateMissingPriceFeeds?: boolean;
|
|
130
|
-
}
|
|
135
|
+
};
|
|
136
|
+
export declare const initializeSuilendAll: (suiGrpcClient: SuiGrpcClient, markets: {
|
|
137
|
+
suilendClient: SuilendClient;
|
|
138
|
+
lendingMarketMetadata?: LendingMarketMetadata;
|
|
139
|
+
}[], fallbackPythEndpoint?: string, options?: InitializeSuilendOptions) => Promise<{
|
|
140
|
+
lendingMarket: {
|
|
141
|
+
version: bigint;
|
|
142
|
+
reserves: {
|
|
143
|
+
config: {
|
|
144
|
+
$typeName: string;
|
|
145
|
+
openLtvPct: number;
|
|
146
|
+
closeLtvPct: number;
|
|
147
|
+
maxCloseLtvPct: number;
|
|
148
|
+
borrowWeightBps: BigNumber;
|
|
149
|
+
depositLimit: BigNumber;
|
|
150
|
+
borrowLimit: BigNumber;
|
|
151
|
+
liquidationBonusBps: number;
|
|
152
|
+
maxLiquidationBonusBps: number;
|
|
153
|
+
depositLimitUsd: BigNumber;
|
|
154
|
+
borrowLimitUsd: BigNumber;
|
|
155
|
+
borrowFeeBps: number;
|
|
156
|
+
spreadFeeBps: number;
|
|
157
|
+
protocolLiquidationFeeBps: number;
|
|
158
|
+
isolated: boolean;
|
|
159
|
+
openAttributedBorrowLimitUsd: number;
|
|
160
|
+
closeAttributedBorrowLimitUsd: number;
|
|
161
|
+
interestRate: {
|
|
162
|
+
id: string;
|
|
163
|
+
utilPercent: BigNumber;
|
|
164
|
+
aprPercent: BigNumber;
|
|
165
|
+
}[];
|
|
166
|
+
};
|
|
167
|
+
$typeName: string;
|
|
168
|
+
id: string;
|
|
169
|
+
arrayIndex: bigint;
|
|
170
|
+
coinType: string;
|
|
171
|
+
mintDecimals: number;
|
|
172
|
+
priceIdentifier: string;
|
|
173
|
+
price: BigNumber;
|
|
174
|
+
smoothedPrice: BigNumber;
|
|
175
|
+
minPrice: BigNumber;
|
|
176
|
+
maxPrice: BigNumber;
|
|
177
|
+
priceLastUpdateTimestampS: bigint;
|
|
178
|
+
availableAmount: BigNumber;
|
|
179
|
+
ctokenSupply: BigNumber;
|
|
180
|
+
borrowedAmount: BigNumber;
|
|
181
|
+
cumulativeBorrowRate: BigNumber;
|
|
182
|
+
interestLastUpdateTimestampS: bigint;
|
|
183
|
+
unclaimedSpreadFees: BigNumber;
|
|
184
|
+
attributedBorrowValue: BigNumber;
|
|
185
|
+
depositsPoolRewardManager: {
|
|
186
|
+
$typeName: string;
|
|
187
|
+
id: string;
|
|
188
|
+
totalShares: bigint;
|
|
189
|
+
poolRewards: {
|
|
190
|
+
$typeName: string;
|
|
191
|
+
id: string;
|
|
192
|
+
poolRewardManagerId: string;
|
|
193
|
+
coinType: string;
|
|
194
|
+
startTimeMs: number;
|
|
195
|
+
endTimeMs: number;
|
|
196
|
+
totalRewards: BigNumber;
|
|
197
|
+
allocatedRewards: BigNumber;
|
|
198
|
+
cumulativeRewardsPerShare: BigNumber;
|
|
199
|
+
numUserRewardManagers: bigint;
|
|
200
|
+
rewardIndex: number;
|
|
201
|
+
symbol: string;
|
|
202
|
+
mintDecimals: number;
|
|
203
|
+
}[];
|
|
204
|
+
lastUpdateTimeMs: bigint;
|
|
205
|
+
};
|
|
206
|
+
borrowsPoolRewardManager: {
|
|
207
|
+
$typeName: string;
|
|
208
|
+
id: string;
|
|
209
|
+
totalShares: bigint;
|
|
210
|
+
poolRewards: {
|
|
211
|
+
$typeName: string;
|
|
212
|
+
id: string;
|
|
213
|
+
poolRewardManagerId: string;
|
|
214
|
+
coinType: string;
|
|
215
|
+
startTimeMs: number;
|
|
216
|
+
endTimeMs: number;
|
|
217
|
+
totalRewards: BigNumber;
|
|
218
|
+
allocatedRewards: BigNumber;
|
|
219
|
+
cumulativeRewardsPerShare: BigNumber;
|
|
220
|
+
numUserRewardManagers: bigint;
|
|
221
|
+
rewardIndex: number;
|
|
222
|
+
symbol: string;
|
|
223
|
+
mintDecimals: number;
|
|
224
|
+
}[];
|
|
225
|
+
lastUpdateTimeMs: bigint;
|
|
226
|
+
};
|
|
227
|
+
availableAmountUsd: BigNumber;
|
|
228
|
+
borrowedAmountUsd: BigNumber;
|
|
229
|
+
depositedAmount: BigNumber;
|
|
230
|
+
depositedAmountUsd: BigNumber;
|
|
231
|
+
cTokenExchangeRate: BigNumber;
|
|
232
|
+
borrowAprPercent: BigNumber;
|
|
233
|
+
depositAprPercent: BigNumber;
|
|
234
|
+
utilizationPercent: BigNumber;
|
|
235
|
+
token: {
|
|
236
|
+
id: string | null;
|
|
237
|
+
decimals: number;
|
|
238
|
+
name: string;
|
|
239
|
+
symbol: string;
|
|
240
|
+
description: string;
|
|
241
|
+
iconUrl: string | null;
|
|
242
|
+
coinType: string;
|
|
243
|
+
};
|
|
244
|
+
symbol: string;
|
|
245
|
+
name: string;
|
|
246
|
+
iconUrl: string | null;
|
|
247
|
+
description: string;
|
|
248
|
+
totalDeposits: BigNumber;
|
|
249
|
+
}[];
|
|
250
|
+
obligations: import("../_generated/_dependencies/source/0x2/object-table/structs").ObjectTable<"0x2::object::ID", `0x1f54a9a2d71799553197e9ea24557797c6398d6a65f2d4d3818c9304b75d5e21::obligation::Obligation<${string}>` | `0xf95b06141ed4a174f239417323bde3f209b972f5930d8521ea38a52aff3a6ddf::obligation::Obligation<${string}>`>;
|
|
251
|
+
rateLimiter: {
|
|
252
|
+
config: {
|
|
253
|
+
windowDuration: bigint;
|
|
254
|
+
maxOutflow: bigint;
|
|
255
|
+
};
|
|
256
|
+
$typeName: string;
|
|
257
|
+
prevQty: bigint;
|
|
258
|
+
windowStart: bigint;
|
|
259
|
+
curQty: bigint;
|
|
260
|
+
remainingOutflow: BigNumber;
|
|
261
|
+
};
|
|
262
|
+
feeReceiver: string;
|
|
263
|
+
badDebtUsd: BigNumber;
|
|
264
|
+
badDebtLimitUsd: BigNumber;
|
|
265
|
+
depositedAmountUsd: BigNumber;
|
|
266
|
+
borrowedAmountUsd: BigNumber;
|
|
267
|
+
tvlUsd: BigNumber;
|
|
268
|
+
id: string;
|
|
269
|
+
type: string;
|
|
270
|
+
ownerCapId: string | undefined;
|
|
271
|
+
name: string;
|
|
272
|
+
isHidden: boolean;
|
|
273
|
+
iconUrl: string | null;
|
|
274
|
+
description: string | null;
|
|
275
|
+
totalSupplyUsd: BigNumber;
|
|
276
|
+
totalBorrowUsd: BigNumber;
|
|
277
|
+
};
|
|
278
|
+
coinMetadataMap: Record<string, import("@suilend/sui-core").Token>;
|
|
279
|
+
refreshedRawReserves: Reserve<string>[];
|
|
280
|
+
unpricedCoinTypes: string[];
|
|
281
|
+
reserveMap: Record<string, {
|
|
282
|
+
config: {
|
|
283
|
+
$typeName: string;
|
|
284
|
+
openLtvPct: number;
|
|
285
|
+
closeLtvPct: number;
|
|
286
|
+
maxCloseLtvPct: number;
|
|
287
|
+
borrowWeightBps: BigNumber;
|
|
288
|
+
depositLimit: BigNumber;
|
|
289
|
+
borrowLimit: BigNumber;
|
|
290
|
+
liquidationBonusBps: number;
|
|
291
|
+
maxLiquidationBonusBps: number;
|
|
292
|
+
depositLimitUsd: BigNumber;
|
|
293
|
+
borrowLimitUsd: BigNumber;
|
|
294
|
+
borrowFeeBps: number;
|
|
295
|
+
spreadFeeBps: number;
|
|
296
|
+
protocolLiquidationFeeBps: number;
|
|
297
|
+
isolated: boolean;
|
|
298
|
+
openAttributedBorrowLimitUsd: number;
|
|
299
|
+
closeAttributedBorrowLimitUsd: number;
|
|
300
|
+
interestRate: {
|
|
301
|
+
id: string;
|
|
302
|
+
utilPercent: BigNumber;
|
|
303
|
+
aprPercent: BigNumber;
|
|
304
|
+
}[];
|
|
305
|
+
};
|
|
306
|
+
$typeName: string;
|
|
307
|
+
id: string;
|
|
308
|
+
arrayIndex: bigint;
|
|
309
|
+
coinType: string;
|
|
310
|
+
mintDecimals: number;
|
|
311
|
+
priceIdentifier: string;
|
|
312
|
+
price: BigNumber;
|
|
313
|
+
smoothedPrice: BigNumber;
|
|
314
|
+
minPrice: BigNumber;
|
|
315
|
+
maxPrice: BigNumber;
|
|
316
|
+
priceLastUpdateTimestampS: bigint;
|
|
317
|
+
availableAmount: BigNumber;
|
|
318
|
+
ctokenSupply: BigNumber;
|
|
319
|
+
borrowedAmount: BigNumber;
|
|
320
|
+
cumulativeBorrowRate: BigNumber;
|
|
321
|
+
interestLastUpdateTimestampS: bigint;
|
|
322
|
+
unclaimedSpreadFees: BigNumber;
|
|
323
|
+
attributedBorrowValue: BigNumber;
|
|
324
|
+
depositsPoolRewardManager: {
|
|
325
|
+
$typeName: string;
|
|
326
|
+
id: string;
|
|
327
|
+
totalShares: bigint;
|
|
328
|
+
poolRewards: {
|
|
329
|
+
$typeName: string;
|
|
330
|
+
id: string;
|
|
331
|
+
poolRewardManagerId: string;
|
|
332
|
+
coinType: string;
|
|
333
|
+
startTimeMs: number;
|
|
334
|
+
endTimeMs: number;
|
|
335
|
+
totalRewards: BigNumber;
|
|
336
|
+
allocatedRewards: BigNumber;
|
|
337
|
+
cumulativeRewardsPerShare: BigNumber;
|
|
338
|
+
numUserRewardManagers: bigint;
|
|
339
|
+
rewardIndex: number;
|
|
340
|
+
symbol: string;
|
|
341
|
+
mintDecimals: number;
|
|
342
|
+
}[];
|
|
343
|
+
lastUpdateTimeMs: bigint;
|
|
344
|
+
};
|
|
345
|
+
borrowsPoolRewardManager: {
|
|
346
|
+
$typeName: string;
|
|
347
|
+
id: string;
|
|
348
|
+
totalShares: bigint;
|
|
349
|
+
poolRewards: {
|
|
350
|
+
$typeName: string;
|
|
351
|
+
id: string;
|
|
352
|
+
poolRewardManagerId: string;
|
|
353
|
+
coinType: string;
|
|
354
|
+
startTimeMs: number;
|
|
355
|
+
endTimeMs: number;
|
|
356
|
+
totalRewards: BigNumber;
|
|
357
|
+
allocatedRewards: BigNumber;
|
|
358
|
+
cumulativeRewardsPerShare: BigNumber;
|
|
359
|
+
numUserRewardManagers: bigint;
|
|
360
|
+
rewardIndex: number;
|
|
361
|
+
symbol: string;
|
|
362
|
+
mintDecimals: number;
|
|
363
|
+
}[];
|
|
364
|
+
lastUpdateTimeMs: bigint;
|
|
365
|
+
};
|
|
366
|
+
availableAmountUsd: BigNumber;
|
|
367
|
+
borrowedAmountUsd: BigNumber;
|
|
368
|
+
depositedAmount: BigNumber;
|
|
369
|
+
depositedAmountUsd: BigNumber;
|
|
370
|
+
cTokenExchangeRate: BigNumber;
|
|
371
|
+
borrowAprPercent: BigNumber;
|
|
372
|
+
depositAprPercent: BigNumber;
|
|
373
|
+
utilizationPercent: BigNumber;
|
|
374
|
+
token: {
|
|
375
|
+
id: string | null;
|
|
376
|
+
decimals: number;
|
|
377
|
+
name: string;
|
|
378
|
+
symbol: string;
|
|
379
|
+
description: string;
|
|
380
|
+
iconUrl: string | null;
|
|
381
|
+
coinType: string;
|
|
382
|
+
};
|
|
383
|
+
symbol: string;
|
|
384
|
+
name: string;
|
|
385
|
+
iconUrl: string | null;
|
|
386
|
+
description: string;
|
|
387
|
+
totalDeposits: BigNumber;
|
|
388
|
+
}>;
|
|
389
|
+
reserveCoinTypes: string[];
|
|
390
|
+
reserveCoinMetadataMap: Record<string, import("@suilend/sui-core").Token>;
|
|
391
|
+
rewardCoinTypes: string[];
|
|
392
|
+
activeRewardCoinTypes: string[];
|
|
393
|
+
rewardCoinMetadataMap: Record<string, import("@suilend/sui-core").Token>;
|
|
394
|
+
}[]>;
|
|
395
|
+
export declare const initializeSuilend: (suiGrpcClient: SuiGrpcClient, suilendClient: SuilendClient, lendingMarketMetadata?: LendingMarketMetadata, fallbackPythEndpoint?: string, options?: InitializeSuilendOptions) => Promise<{
|
|
131
396
|
lendingMarket: {
|
|
132
397
|
version: bigint;
|
|
133
398
|
reserves: {
|
|
@@ -383,7 +648,13 @@ export declare const initializeSuilend: (suiGrpcClient: SuiGrpcClient, suilendCl
|
|
|
383
648
|
activeRewardCoinTypes: string[];
|
|
384
649
|
rewardCoinMetadataMap: Record<string, import("@suilend/sui-core").Token>;
|
|
385
650
|
}>;
|
|
386
|
-
export declare const
|
|
651
|
+
export declare const initializeSuilendRewardsAll: (markets: {
|
|
652
|
+
reserveMap: Record<string, ParsedReserve>;
|
|
653
|
+
activeRewardCoinTypes: string[];
|
|
654
|
+
}[], fetchPrices?: (coinTypes: string[]) => Promise<Record<string, number | undefined>>) => Promise<{
|
|
655
|
+
rewardPriceMap: Record<string, BigNumber | undefined>;
|
|
656
|
+
}[]>;
|
|
657
|
+
export declare const initializeSuilendRewards: (reserveMap: Record<string, ParsedReserve>, activeRewardCoinTypes: string[], fetchPrices?: (coinTypes: string[]) => Promise<Record<string, number | undefined>>) => Promise<{
|
|
387
658
|
rewardPriceMap: Record<string, BigNumber | undefined>;
|
|
388
659
|
}>;
|
|
389
660
|
export declare const initializeObligations: (suiGrpcClient: SuiGrpcClient, suilendClient: SuilendClient, refreshedRawReserves: Reserve<string>[], reserveMap: Record<string, ParsedReserve>, address?: string) => Promise<{
|
package/lib/initialize.js
CHANGED
|
@@ -97,8 +97,7 @@ const TREATS_COINTYPE = "0x0dadb7fa2771c2952f96161fc1f0c105d1f22d53926b9ff2498a8
|
|
|
97
97
|
export const NORMALIZED_MAYA_COINTYPE = normalizeStructTag(MAYA_COINTYPE);
|
|
98
98
|
export const NORMALIZED_mPOINTS_COINTYPE = normalizeStructTag(mPOINTS_COINTYPE);
|
|
99
99
|
export const NORMALIZED_TREATS_COINTYPE = normalizeStructTag(TREATS_COINTYPE);
|
|
100
|
-
export const resolveReserveOrder = (lendingMarketId, lendingMarketMetadata) => lendingMarketMetadata?.reserveOrder ??
|
|
101
|
-
RESERVES_CUSTOM_ORDER[lendingMarketId];
|
|
100
|
+
export const resolveReserveOrder = (lendingMarketId, lendingMarketMetadata) => lendingMarketMetadata?.reserveOrder ?? RESERVES_CUSTOM_ORDER[lendingMarketId];
|
|
102
101
|
const reserveRanks = (order) => {
|
|
103
102
|
const ranks = new Map();
|
|
104
103
|
order.forEach((coinType, index) => {
|
|
@@ -147,14 +146,8 @@ export const resolveCoinMetadataForInitialize = async (miscCoinTypes, reserveCoi
|
|
|
147
146
|
rewardCoinMetadataMap: pick(rewardCoinTypes),
|
|
148
147
|
};
|
|
149
148
|
};
|
|
150
|
-
|
|
151
|
-
|
|
152
|
-
*
|
|
153
|
-
* Extracted so both branches are directly testable — the difference between
|
|
154
|
-
* them is a throw, which is the whole reason the flag exists and is exactly
|
|
155
|
-
* what a regression here would silently change.
|
|
156
|
-
*/
|
|
157
|
-
export const refreshReservesForInitialize = async (reserves, pythConnection, tolerateMissingPriceFeeds) => {
|
|
149
|
+
export const refreshReservesForInitializeAll = async (reservesByMarket, pythConnection, tolerateMissingPriceFeeds) => {
|
|
150
|
+
const allReserves = reservesByMarket.flat();
|
|
158
151
|
if (tolerateMissingPriceFeeds) {
|
|
159
152
|
// Tolerance is only expressible over a connection that can report WHICH
|
|
160
153
|
// feeds it could not serve. A plain all-or-nothing source cannot — the
|
|
@@ -174,14 +167,27 @@ export const refreshReservesForInitialize = async (reserves, pythConnection, tol
|
|
|
174
167
|
if (!simulate.canReportMissingPriceFeeds(pythConnection)) {
|
|
175
168
|
throw new Error(`tolerateMissingPriceFeeds requires a connection that can report per-feed gaps. The connection in use is all-or-nothing, so an endpoint that rejects the whole batch over one unknown feed would fail initialization regardless of this flag — ${simulate.PARTIAL_FETCH_REQUIRED_HINT}. Note that patching a client prototype does NOT satisfy this: it can reroute getLatestPriceFeeds but cannot add a per-feed gap report.`);
|
|
176
169
|
}
|
|
177
|
-
|
|
170
|
+
const priceFeeds = await simulate.fetchReservePriceFeedsTolerant(allReserves, pythConnection);
|
|
171
|
+
return reservesByMarket.map((reserves) => simulate.applyReservePriceFeedsTolerant(reserves, priceFeeds));
|
|
178
172
|
}
|
|
179
173
|
// Default path, behaviourally identical to what this function did before the
|
|
180
174
|
// flag existed: throws if any requested feed is absent from the response.
|
|
181
|
-
|
|
182
|
-
|
|
175
|
+
const priceFeeds = await pythConnection.getLatestPriceFeeds(simulate.reservePriceFeedIds(allReserves));
|
|
176
|
+
return reservesByMarket.map((reserves) => ({
|
|
177
|
+
reserves: simulate.applyReservePriceFeeds(reserves, priceFeeds),
|
|
183
178
|
unpricedCoinTypes: [],
|
|
184
|
-
};
|
|
179
|
+
}));
|
|
180
|
+
};
|
|
181
|
+
/**
|
|
182
|
+
* Price reserves for `initializeSuilend`, honouring the opt-in tolerance flag.
|
|
183
|
+
*
|
|
184
|
+
* Extracted so both branches are directly testable — the difference between
|
|
185
|
+
* them is a throw, which is the whole reason the flag exists and is exactly
|
|
186
|
+
* what a regression here would silently change.
|
|
187
|
+
*/
|
|
188
|
+
export const refreshReservesForInitialize = async (reserves, pythConnection, tolerateMissingPriceFeeds) => {
|
|
189
|
+
const [refreshed] = await refreshReservesForInitializeAll([reserves], pythConnection, tolerateMissingPriceFeeds);
|
|
190
|
+
return refreshed;
|
|
185
191
|
};
|
|
186
192
|
/**
|
|
187
193
|
* Price the temporary-Pyth-feed reserves from the cached-price service, in
|
|
@@ -223,121 +229,141 @@ export const priceTemporaryPythFeedReserves = async (reserves, fetchPrices = get
|
|
|
223
229
|
});
|
|
224
230
|
return fabricatedPriceCoinTypes;
|
|
225
231
|
};
|
|
226
|
-
export const
|
|
232
|
+
export const priceTemporaryPythFeedReservesAll = async (reservesByMarket, fetchPrices = getPrices) => {
|
|
233
|
+
const fabricatedPriceCoinTypes = new Set(await priceTemporaryPythFeedReserves(reservesByMarket.flat(), fetchPrices));
|
|
234
|
+
return reservesByMarket.map((reserves) => Array.from(new Set(reserves.map((reserve) => normalizeStructTag(reserve.coinType.name)))).filter((coinType) => fabricatedPriceCoinTypes.has(coinType)));
|
|
235
|
+
};
|
|
236
|
+
export const initializeSuilendAll = async (suiGrpcClient, markets, fallbackPythEndpoint, options) => {
|
|
227
237
|
const nowMs = Date.now();
|
|
228
238
|
const nowS = Math.floor(nowMs / 1000);
|
|
229
|
-
const
|
|
230
|
-
|
|
231
|
-
|
|
232
|
-
|
|
233
|
-
|
|
234
|
-
|
|
235
|
-
|
|
236
|
-
|
|
237
|
-
|
|
238
|
-
|
|
239
|
+
const reservesByMarket = markets.map(({ suilendClient }) => {
|
|
240
|
+
const withoutTemporaryPythPriceFeeds = [];
|
|
241
|
+
const withTemporaryPythPriceFeeds = [];
|
|
242
|
+
for (const rawReserve of suilendClient.lendingMarket.reserves) {
|
|
243
|
+
const reserve = simulate.compoundReserveInterest(rawReserve, nowS);
|
|
244
|
+
if (TEMPORARY_PYTH_PRICE_FEED_COINTYPES.includes(normalizeStructTag(reserve.coinType.name))) {
|
|
245
|
+
withTemporaryPythPriceFeeds.push(reserve);
|
|
246
|
+
}
|
|
247
|
+
else {
|
|
248
|
+
withoutTemporaryPythPriceFeeds.push(reserve);
|
|
249
|
+
}
|
|
239
250
|
}
|
|
240
|
-
|
|
251
|
+
return { withoutTemporaryPythPriceFeeds, withTemporaryPythPriceFeeds };
|
|
252
|
+
});
|
|
241
253
|
const pythConnection = await resolvePythConnection(options?.pythConnection, fallbackPythEndpoint, options?.pythConnectionConfig);
|
|
242
|
-
const [
|
|
243
|
-
|
|
244
|
-
|
|
254
|
+
const [refreshedWithoutTemporaryByMarket, fabricatedPriceCoinTypesByMarket] = await Promise.all([
|
|
255
|
+
refreshReservesForInitializeAll(reservesByMarket.map((r) => r.withoutTemporaryPythPriceFeeds), pythConnection, options?.tolerateMissingPriceFeeds),
|
|
256
|
+
priceTemporaryPythFeedReservesAll(reservesByMarket.map((r) => r.withTemporaryPythPriceFeeds)),
|
|
245
257
|
]);
|
|
246
|
-
|
|
247
|
-
|
|
248
|
-
|
|
249
|
-
|
|
250
|
-
|
|
251
|
-
|
|
252
|
-
|
|
253
|
-
|
|
254
|
-
|
|
255
|
-
|
|
256
|
-
|
|
257
|
-
|
|
258
|
-
|
|
259
|
-
|
|
260
|
-
|
|
261
|
-
|
|
262
|
-
|
|
263
|
-
|
|
264
|
-
|
|
265
|
-
|
|
266
|
-
|
|
267
|
-
|
|
268
|
-
|
|
269
|
-
|
|
270
|
-
|
|
271
|
-
|
|
272
|
-
|
|
273
|
-
|
|
274
|
-
|
|
275
|
-
|
|
276
|
-
|
|
277
|
-
|
|
278
|
-
|
|
279
|
-
|
|
280
|
-
|
|
281
|
-
|
|
282
|
-
|
|
283
|
-
|
|
284
|
-
|
|
285
|
-
|
|
286
|
-
|
|
287
|
-
|
|
288
|
-
|
|
289
|
-
|
|
258
|
+
return Promise.all(markets.map(async ({ suilendClient, lendingMarketMetadata }, index) => {
|
|
259
|
+
const refreshedRawReserves = [
|
|
260
|
+
...refreshedWithoutTemporaryByMarket[index].reserves,
|
|
261
|
+
...reservesByMarket[index].withTemporaryPythPriceFeeds,
|
|
262
|
+
];
|
|
263
|
+
// Reserves NOT carrying a real refreshed price, from either cause: no endpoint
|
|
264
|
+
// served the Pyth feed (needs `tolerateMissingPriceFeeds`, else the refresh
|
|
265
|
+
// threw), or a temporary-feed reserve fell back to a fabricated placeholder
|
|
266
|
+
// (independent of that flag). Surfaced rather than logged-and-forgotten: only
|
|
267
|
+
// the caller knows whether a given reserve can safely hold such a price.
|
|
268
|
+
//
|
|
269
|
+
// Normalized, matching `reserveMap` / `reserveCoinTypes` keys. Entries in
|
|
270
|
+
// `refreshedRawReserves` carry the RAW on-chain `coinType.name`, which never
|
|
271
|
+
// compares equal to these (no `0x` prefix), so filtering that array means
|
|
272
|
+
// normalizing first:
|
|
273
|
+
//
|
|
274
|
+
// const unpriced = new Set(unpricedCoinTypes);
|
|
275
|
+
// refreshedRawReserves.filter(
|
|
276
|
+
// (r) => !unpriced.has(normalizeStructTag(r.coinType.name)),
|
|
277
|
+
// );
|
|
278
|
+
const unpricedCoinTypes = [
|
|
279
|
+
...refreshedWithoutTemporaryByMarket[index].unpricedCoinTypes,
|
|
280
|
+
...fabricatedPriceCoinTypesByMarket[index],
|
|
281
|
+
];
|
|
282
|
+
const miscCoinTypes = [
|
|
283
|
+
NORMALIZED_SEND_POINTS_S1_COINTYPE,
|
|
284
|
+
NORMALIZED_SEND_POINTS_S2_COINTYPE,
|
|
285
|
+
NORMALIZED_SEND_COINTYPE,
|
|
286
|
+
];
|
|
287
|
+
const reserveCoinTypes = [];
|
|
288
|
+
const rewardCoinTypes = [];
|
|
289
|
+
const activeRewardCoinTypes = [];
|
|
290
|
+
refreshedRawReserves.forEach((r) => {
|
|
291
|
+
reserveCoinTypes.push(normalizeStructTag(r.coinType.name));
|
|
292
|
+
[
|
|
293
|
+
...r.depositsPoolRewardManager.poolRewards,
|
|
294
|
+
...r.borrowsPoolRewardManager.poolRewards,
|
|
295
|
+
].forEach((pr) => {
|
|
296
|
+
if (!pr)
|
|
297
|
+
return;
|
|
298
|
+
const isActive = nowMs >= Number(pr.startTimeMs) && nowMs < Number(pr.endTimeMs);
|
|
299
|
+
rewardCoinTypes.push(normalizeStructTag(pr.coinType.name));
|
|
300
|
+
if (isActive)
|
|
301
|
+
activeRewardCoinTypes.push(normalizeStructTag(pr.coinType.name));
|
|
302
|
+
});
|
|
290
303
|
});
|
|
304
|
+
const uniqueMiscCoinTypes = Array.from(new Set(miscCoinTypes));
|
|
305
|
+
const uniqueReserveCoinTypes = Array.from(new Set(reserveCoinTypes));
|
|
306
|
+
const uniqueRewardCoinTypes = Array.from(new Set(rewardCoinTypes));
|
|
307
|
+
const uniqueActiveRewardsCoinTypes = Array.from(new Set(activeRewardCoinTypes));
|
|
308
|
+
const { coinMetadataMap, reserveCoinMetadataMap, rewardCoinMetadataMap } = await resolveCoinMetadataForInitialize(uniqueMiscCoinTypes, uniqueReserveCoinTypes, uniqueRewardCoinTypes);
|
|
309
|
+
// const walReserve = refreshedRawReserves.find(
|
|
310
|
+
// (r) => normalizeStructTag(r.coinType.name) === NORMALIZED_WAL_COINTYPE,
|
|
311
|
+
// );
|
|
312
|
+
// if (walReserve) {
|
|
313
|
+
// const walPrice = BigInt(
|
|
314
|
+
// +new BigNumber(0.35).times(WAD).integerValue(BigNumber.ROUND_DOWN),
|
|
315
|
+
// );
|
|
316
|
+
// (walReserve.price.value as bigint) = walPrice;
|
|
317
|
+
// (walReserve.smoothedPrice.value as bigint) = walPrice;
|
|
318
|
+
// }
|
|
319
|
+
const lendingMarket = parseLendingMarket(suilendClient.lendingMarket, refreshedRawReserves, coinMetadataMap, nowS, lendingMarketMetadata);
|
|
320
|
+
const reserveOrder = resolveReserveOrder(lendingMarket.id, lendingMarketMetadata);
|
|
321
|
+
if (reserveOrder) {
|
|
322
|
+
lendingMarket.reserves = sortReservesByOrder(lendingMarket.reserves, reserveOrder);
|
|
323
|
+
}
|
|
324
|
+
const reserveMap = lendingMarket.reserves.reduce((acc, reserve) => ({ ...acc, [reserve.coinType]: reserve }), {});
|
|
325
|
+
return {
|
|
326
|
+
lendingMarket,
|
|
327
|
+
coinMetadataMap,
|
|
328
|
+
refreshedRawReserves,
|
|
329
|
+
unpricedCoinTypes,
|
|
330
|
+
reserveMap,
|
|
331
|
+
reserveCoinTypes: uniqueReserveCoinTypes,
|
|
332
|
+
reserveCoinMetadataMap,
|
|
333
|
+
rewardCoinTypes: uniqueRewardCoinTypes,
|
|
334
|
+
activeRewardCoinTypes: uniqueActiveRewardsCoinTypes,
|
|
335
|
+
rewardCoinMetadataMap,
|
|
336
|
+
};
|
|
337
|
+
}));
|
|
338
|
+
};
|
|
339
|
+
export const initializeSuilend = async (suiGrpcClient, suilendClient, lendingMarketMetadata, fallbackPythEndpoint, options) => {
|
|
340
|
+
const [initialized] = await initializeSuilendAll(suiGrpcClient, [{ suilendClient, lendingMarketMetadata }], fallbackPythEndpoint, options);
|
|
341
|
+
return initialized;
|
|
342
|
+
};
|
|
343
|
+
export const initializeSuilendRewardsAll = async (markets, fetchPrices = getPrices) => {
|
|
344
|
+
const seeded = markets.map(({ reserveMap, activeRewardCoinTypes }) => {
|
|
345
|
+
const rewardPriceMap = Object.entries(reserveMap).reduce((acc, [coinType, reserve]) => ({ ...acc, [coinType]: reserve.price }), {});
|
|
346
|
+
rewardPriceMap[NORMALIZED_TREATS_COINTYPE] = new BigNumber(0.1);
|
|
347
|
+
const reservelessActiveRewardCoinTypes = activeRewardCoinTypes.filter((coinType) => !(isSendPoints(coinType) ||
|
|
348
|
+
isSteammPoints(coinType) ||
|
|
349
|
+
coinType === NORMALIZED_MAYA_COINTYPE ||
|
|
350
|
+
coinType === NORMALIZED_mPOINTS_COINTYPE) && !rewardPriceMap[coinType]);
|
|
351
|
+
return { rewardPriceMap, reservelessActiveRewardCoinTypes };
|
|
352
|
+
});
|
|
353
|
+
const cachedUsdPrices = await fetchPrices(Array.from(new Set(seeded.flatMap((m) => m.reservelessActiveRewardCoinTypes))));
|
|
354
|
+
return seeded.map(({ rewardPriceMap, reservelessActiveRewardCoinTypes }) => {
|
|
355
|
+
for (const coinType of reservelessActiveRewardCoinTypes) {
|
|
356
|
+
const cachedUsdPrice = cachedUsdPrices[coinType];
|
|
357
|
+
if (cachedUsdPrice === undefined)
|
|
358
|
+
continue;
|
|
359
|
+
rewardPriceMap[coinType] = new BigNumber(cachedUsdPrice);
|
|
360
|
+
}
|
|
361
|
+
return { rewardPriceMap };
|
|
291
362
|
});
|
|
292
|
-
const uniqueMiscCoinTypes = Array.from(new Set(miscCoinTypes));
|
|
293
|
-
const uniqueReserveCoinTypes = Array.from(new Set(reserveCoinTypes));
|
|
294
|
-
const uniqueRewardCoinTypes = Array.from(new Set(rewardCoinTypes));
|
|
295
|
-
const uniqueActiveRewardsCoinTypes = Array.from(new Set(activeRewardCoinTypes));
|
|
296
|
-
const { coinMetadataMap, reserveCoinMetadataMap, rewardCoinMetadataMap } = await resolveCoinMetadataForInitialize(uniqueMiscCoinTypes, uniqueReserveCoinTypes, uniqueRewardCoinTypes);
|
|
297
|
-
// const walReserve = refreshedRawReserves.find(
|
|
298
|
-
// (r) => normalizeStructTag(r.coinType.name) === NORMALIZED_WAL_COINTYPE,
|
|
299
|
-
// );
|
|
300
|
-
// if (walReserve) {
|
|
301
|
-
// const walPrice = BigInt(
|
|
302
|
-
// +new BigNumber(0.35).times(WAD).integerValue(BigNumber.ROUND_DOWN),
|
|
303
|
-
// );
|
|
304
|
-
// (walReserve.price.value as bigint) = walPrice;
|
|
305
|
-
// (walReserve.smoothedPrice.value as bigint) = walPrice;
|
|
306
|
-
// }
|
|
307
|
-
const lendingMarket = parseLendingMarket(suilendClient.lendingMarket, refreshedRawReserves, coinMetadataMap, nowS, lendingMarketMetadata);
|
|
308
|
-
const reserveOrder = resolveReserveOrder(lendingMarket.id, lendingMarketMetadata);
|
|
309
|
-
if (reserveOrder) {
|
|
310
|
-
lendingMarket.reserves = sortReservesByOrder(lendingMarket.reserves, reserveOrder);
|
|
311
|
-
}
|
|
312
|
-
const reserveMap = lendingMarket.reserves.reduce((acc, reserve) => ({ ...acc, [reserve.coinType]: reserve }), {});
|
|
313
|
-
return {
|
|
314
|
-
lendingMarket,
|
|
315
|
-
coinMetadataMap,
|
|
316
|
-
refreshedRawReserves,
|
|
317
|
-
unpricedCoinTypes,
|
|
318
|
-
reserveMap,
|
|
319
|
-
reserveCoinTypes: uniqueReserveCoinTypes,
|
|
320
|
-
reserveCoinMetadataMap,
|
|
321
|
-
rewardCoinTypes: uniqueRewardCoinTypes,
|
|
322
|
-
activeRewardCoinTypes: uniqueActiveRewardsCoinTypes,
|
|
323
|
-
rewardCoinMetadataMap,
|
|
324
|
-
};
|
|
325
363
|
};
|
|
326
|
-
export const initializeSuilendRewards = async (reserveMap, activeRewardCoinTypes) => {
|
|
327
|
-
const
|
|
328
|
-
|
|
329
|
-
const reservelessActiveRewardCoinTypes = activeRewardCoinTypes.filter((coinType) => !(isSendPoints(coinType) ||
|
|
330
|
-
isSteammPoints(coinType) ||
|
|
331
|
-
coinType === NORMALIZED_MAYA_COINTYPE ||
|
|
332
|
-
coinType === NORMALIZED_mPOINTS_COINTYPE) && !rewardPriceMap[coinType]);
|
|
333
|
-
const reservelessActiveRewardCachedUsdPrices = await getPrices(reservelessActiveRewardCoinTypes);
|
|
334
|
-
for (const coinType of reservelessActiveRewardCoinTypes) {
|
|
335
|
-
const cachedUsdPrice = reservelessActiveRewardCachedUsdPrices[coinType];
|
|
336
|
-
if (cachedUsdPrice === undefined)
|
|
337
|
-
continue;
|
|
338
|
-
rewardPriceMap[coinType] = new BigNumber(cachedUsdPrice);
|
|
339
|
-
}
|
|
340
|
-
return { rewardPriceMap };
|
|
364
|
+
export const initializeSuilendRewards = async (reserveMap, activeRewardCoinTypes, fetchPrices = getPrices) => {
|
|
365
|
+
const [rewards] = await initializeSuilendRewardsAll([{ reserveMap, activeRewardCoinTypes }], fetchPrices);
|
|
366
|
+
return rewards;
|
|
341
367
|
};
|
|
342
368
|
export const initializeObligations = async (suiGrpcClient, suilendClient, refreshedRawReserves, reserveMap, address) => {
|
|
343
369
|
if (!address)
|
package/package.json
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"name":"@suilend/sdk","version":"12.
|
|
1
|
+
{"name":"@suilend/sdk","version":"12.1.0","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/utils/simulate.d.ts
CHANGED
|
@@ -45,6 +45,8 @@ export interface PriceFeedSource {
|
|
|
45
45
|
getLatestPriceFeeds(ids: string[]): Promise<FeedPrice[] | undefined>;
|
|
46
46
|
}
|
|
47
47
|
export { normalizeFeedId };
|
|
48
|
+
export declare const reservePriceFeedIds: (reserves: Reserve<string>[]) => string[];
|
|
49
|
+
export declare const applyReservePriceFeeds: (reserves: Reserve<string>[], priceFeeds: FeedPrice[] | undefined) => Reserve<string>[];
|
|
48
50
|
export declare const refreshReservePrice: (reserves: Reserve<string>[], pythConnection: PriceFeedSource) => Promise<Reserve<string>[]>;
|
|
49
51
|
/**
|
|
50
52
|
* A connection that can report which of the requested feeds it could not serve,
|
|
@@ -109,6 +111,11 @@ export declare const PARTIAL_FETCH_REQUIRED_HINT = "inject a connection implemen
|
|
|
109
111
|
* failure is re-thrown with attribution rather than surfacing as a bare HTTP
|
|
110
112
|
* error. `refreshReservesForInitialize` rejects the combination outright.
|
|
111
113
|
*/
|
|
114
|
+
export declare const fetchReservePriceFeedsTolerant: (reserves: Reserve<string>[], pythConnection: PriceFeedSource | PartialPriceFeedSource) => Promise<FeedPrice[] | undefined>;
|
|
115
|
+
export declare const applyReservePriceFeedsTolerant: (reserves: Reserve<string>[], priceFeeds: FeedPrice[] | undefined) => {
|
|
116
|
+
reserves: Reserve<string>[];
|
|
117
|
+
unpricedCoinTypes: string[];
|
|
118
|
+
};
|
|
112
119
|
export declare const refreshReservePriceTolerant: (reserves: Reserve<string>[], pythConnection: PriceFeedSource | PartialPriceFeedSource) => Promise<{
|
|
113
120
|
reserves: Reserve<string>[];
|
|
114
121
|
unpricedCoinTypes: string[];
|
package/utils/simulate.js
CHANGED
|
@@ -160,9 +160,8 @@ const withFeedPrice = (reserve, priceFeed) => {
|
|
|
160
160
|
newReserve.priceLastUpdateTimestampS = BigInt(priceFeed.publishTimeS);
|
|
161
161
|
return newReserve;
|
|
162
162
|
};
|
|
163
|
-
export const
|
|
164
|
-
|
|
165
|
-
const priceFeeds = await pythConnection.getLatestPriceFeeds(priceIdentifiers);
|
|
163
|
+
export const reservePriceFeedIds = (reserves) => Array.from(new Set(reserves.map(reserveFeedId)));
|
|
164
|
+
export const applyReservePriceFeeds = (reserves, priceFeeds) => {
|
|
166
165
|
// Deliberately NOT a throw, and deliberately not expressed over the tolerant
|
|
167
166
|
// function below: consumers outside this repo depend on `undefined` meaning
|
|
168
167
|
// "keep every reserve's existing price". Hazardous (see the tolerant
|
|
@@ -171,16 +170,14 @@ export const refreshReservePrice = async (reserves, pythConnection) => {
|
|
|
171
170
|
if (!priceFeeds)
|
|
172
171
|
return reserves;
|
|
173
172
|
const byId = indexFeedsById(priceFeeds);
|
|
174
|
-
|
|
175
|
-
for (let i = 0; i < reserves.length; i++) {
|
|
176
|
-
const reserve = reserves[i];
|
|
173
|
+
return reserves.map((reserve) => {
|
|
177
174
|
const priceFeed = byId.get(reserveFeedId(reserve));
|
|
178
175
|
if (!priceFeed)
|
|
179
176
|
throw new Error(`Price feed not found for reserve ${reserve.coinType.name}`);
|
|
180
|
-
|
|
181
|
-
}
|
|
182
|
-
return updatedReserves;
|
|
177
|
+
return withFeedPrice(reserve, priceFeed);
|
|
178
|
+
});
|
|
183
179
|
};
|
|
180
|
+
export const refreshReservePrice = async (reserves, pythConnection) => applyReservePriceFeeds(reserves, await pythConnection.getLatestPriceFeeds(reservePriceFeedIds(reserves)));
|
|
184
181
|
/**
|
|
185
182
|
* Whether this connection can report WHICH requested feeds it could not serve.
|
|
186
183
|
*
|
|
@@ -235,24 +232,24 @@ export const PARTIAL_FETCH_REQUIRED_HINT = "inject a connection implementing get
|
|
|
235
232
|
* failure is re-thrown with attribution rather than surfacing as a bare HTTP
|
|
236
233
|
* error. `refreshReservesForInitialize` rejects the combination outright.
|
|
237
234
|
*/
|
|
238
|
-
export const
|
|
239
|
-
const priceIdentifiers =
|
|
240
|
-
let priceFeeds;
|
|
235
|
+
export const fetchReservePriceFeedsTolerant = async (reserves, pythConnection) => {
|
|
236
|
+
const priceIdentifiers = reservePriceFeedIds(reserves);
|
|
241
237
|
if (canReportMissingPriceFeeds(pythConnection)) {
|
|
242
|
-
|
|
238
|
+
return (await pythConnection.getLatestPriceFeedsPartial(priceIdentifiers))
|
|
239
|
+
.feeds;
|
|
243
240
|
}
|
|
244
|
-
|
|
245
|
-
|
|
246
|
-
|
|
247
|
-
|
|
248
|
-
|
|
249
|
-
|
|
250
|
-
|
|
251
|
-
|
|
252
|
-
|
|
253
|
-
throw new Error(`Tolerant price refresh failed on an all-or-nothing connection: ${error instanceof Error ? error.message : String(error)}. This connection cannot report which feeds are missing, so an endpoint that rejects the whole batch over one unknown id (Hermes v2) defeats tolerance entirely — ${PARTIAL_FETCH_REQUIRED_HINT}.`, { cause: error });
|
|
254
|
-
}
|
|
241
|
+
try {
|
|
242
|
+
return await pythConnection.getLatestPriceFeeds(priceIdentifiers);
|
|
243
|
+
}
|
|
244
|
+
catch (error) {
|
|
245
|
+
// Not swallowed — a transport failure must stay a failure, or every
|
|
246
|
+
// reserve silently reports as unpriced and the caller cannot tell a dead
|
|
247
|
+
// endpoint from a retired feed. Re-thrown only to name the cause, which a
|
|
248
|
+
// bare "HTTP error! status: 404" does not.
|
|
249
|
+
throw new Error(`Tolerant price refresh failed on an all-or-nothing connection: ${error instanceof Error ? error.message : String(error)}. This connection cannot report which feeds are missing, so an endpoint that rejects the whole batch over one unknown id (Hermes v2) defeats tolerance entirely — ${PARTIAL_FETCH_REQUIRED_HINT}.`, { cause: error });
|
|
255
250
|
}
|
|
251
|
+
};
|
|
252
|
+
export const applyReservePriceFeedsTolerant = (reserves, priceFeeds) => {
|
|
256
253
|
const byId = indexFeedsById(priceFeeds ?? []);
|
|
257
254
|
const unpricedCoinTypes = [];
|
|
258
255
|
const updatedReserves = reserves.map((reserve) => {
|
|
@@ -270,6 +267,7 @@ export const refreshReservePriceTolerant = async (reserves, pythConnection) => {
|
|
|
270
267
|
});
|
|
271
268
|
return { reserves: updatedReserves, unpricedCoinTypes };
|
|
272
269
|
};
|
|
270
|
+
export const refreshReservePriceTolerant = async (reserves, pythConnection) => applyReservePriceFeedsTolerant(reserves, await fetchReservePriceFeedsTolerant(reserves, pythConnection));
|
|
273
271
|
const warnedStaleRewardSnapshots = new Set();
|
|
274
272
|
const warnStaleRewardSnapshot = (poolRewardId, poolCumulative, userCumulative) => {
|
|
275
273
|
if (warnedStaleRewardSnapshots.has(poolRewardId))
|
|
@@ -532,5 +530,9 @@ export function computeObligationHealthFromPositions(deposits, borrows, refreshe
|
|
|
532
530
|
const borrowWeight = computeBorrowWeight(config);
|
|
533
531
|
weightedBorrowedValueUsd = weightedBorrowedValueUsd.plus(marketValue.multipliedBy(borrowWeight));
|
|
534
532
|
}
|
|
535
|
-
return {
|
|
533
|
+
return {
|
|
534
|
+
depositedValueUsd: depositValueUsd,
|
|
535
|
+
unhealthyBorrowValueUsd,
|
|
536
|
+
weightedBorrowedValueUsd,
|
|
537
|
+
};
|
|
536
538
|
}
|