@suilend/sdk 3.0.8 → 5.0.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/_generated/_dependencies/source/0x1/ascii/structs.d.ts +4 -4
- package/_generated/_dependencies/source/0x1/ascii/structs.js +42 -41
- package/_generated/_dependencies/source/0x1/option/structs.js +22 -26
- package/_generated/_dependencies/source/0x1/type-name/structs.d.ts +3 -3
- package/_generated/_dependencies/source/0x1/type-name/structs.js +21 -25
- package/_generated/_dependencies/source/0x2/bag/structs.d.ts +5 -5
- package/_generated/_dependencies/source/0x2/bag/structs.js +22 -25
- package/_generated/_dependencies/source/0x2/balance/structs.d.ts +4 -4
- package/_generated/_dependencies/source/0x2/balance/structs.js +42 -41
- package/_generated/_dependencies/source/0x2/object/structs.d.ts +5 -5
- package/_generated/_dependencies/source/0x2/object/structs.js +42 -41
- package/_generated/_dependencies/source/0x2/object-table/structs.d.ts +5 -5
- package/_generated/_dependencies/source/0x2/object-table/structs.js +22 -25
- package/_generated/_dependencies/source/0x8d97f1cd6ac663735be08d1d2b6d02a159e711586461306ce60a2b7a6a565a9e/i64/structs.d.ts +3 -3
- package/_generated/_dependencies/source/0x8d97f1cd6ac663735be08d1d2b6d02a159e711586461306ce60a2b7a6a565a9e/i64/structs.js +22 -25
- package/_generated/_dependencies/source/0x8d97f1cd6ac663735be08d1d2b6d02a159e711586461306ce60a2b7a6a565a9e/price/structs.d.ts +9 -9
- package/_generated/_dependencies/source/0x8d97f1cd6ac663735be08d1d2b6d02a159e711586461306ce60a2b7a6a565a9e/price/structs.js +24 -25
- package/_generated/_dependencies/source/0x8d97f1cd6ac663735be08d1d2b6d02a159e711586461306ce60a2b7a6a565a9e/price-feed/structs.d.ts +21 -21
- package/_generated/_dependencies/source/0x8d97f1cd6ac663735be08d1d2b6d02a159e711586461306ce60a2b7a6a565a9e/price-feed/structs.js +23 -25
- package/_generated/_dependencies/source/0x8d97f1cd6ac663735be08d1d2b6d02a159e711586461306ce60a2b7a6a565a9e/price-identifier/structs.d.ts +2 -2
- package/_generated/_dependencies/source/0x8d97f1cd6ac663735be08d1d2b6d02a159e711586461306ce60a2b7a6a565a9e/price-identifier/structs.js +21 -25
- package/_generated/_dependencies/source/0x8d97f1cd6ac663735be08d1d2b6d02a159e711586461306ce60a2b7a6a565a9e/price-info/structs.d.ts +52 -52
- package/_generated/_dependencies/source/0x8d97f1cd6ac663735be08d1d2b6d02a159e711586461306ce60a2b7a6a565a9e/price-info/structs.js +45 -41
- package/_generated/_framework/vector.js +9 -6
- package/_generated/suilend/cell/structs.d.ts +1 -1
- package/_generated/suilend/cell/structs.js +21 -25
- package/_generated/suilend/decimal/structs.d.ts +2 -2
- package/_generated/suilend/decimal/structs.js +21 -25
- package/_generated/suilend/lending-market/structs.d.ts +129 -129
- package/_generated/suilend/lending-market/structs.js +391 -265
- package/_generated/suilend/liquidity-mining/structs.d.ts +44 -44
- package/_generated/suilend/liquidity-mining/structs.js +122 -89
- package/_generated/suilend/obligation/structs.d.ts +98 -98
- package/_generated/suilend/obligation/structs.js +194 -121
- package/_generated/suilend/rate-limiter/structs.d.ts +12 -12
- package/_generated/suilend/rate-limiter/structs.js +46 -41
- package/_generated/suilend/reserve/structs.d.ts +128 -128
- package/_generated/suilend/reserve/structs.js +243 -153
- package/_generated/suilend/reserve-config/structs.d.ts +30 -30
- package/_generated/suilend/reserve-config/structs.js +60 -41
- package/api/events.js +5 -14
- package/client.js +267 -319
- package/lib/initialize.d.ts +85 -3
- package/lib/initialize.js +119 -52
- package/lib/liquidityMining.js +15 -19
- package/lib/pyth.d.ts +16 -0
- package/lib/pyth.js +30 -15
- package/lib/pythAdapter.js +96 -112
- package/lib/strategyOwnerCap.js +8 -17
- package/margin/margin/admin_cap.d.ts +2 -2
- package/margin/margin/deps/std/type_name.d.ts +1 -1
- package/margin/margin/deps/suilend/lending_market.d.ts +2 -2
- package/margin/margin/market.d.ts +11 -11
- package/margin/margin/market.js +22 -22
- package/margin/margin/permissions.js +14 -14
- package/margin/margin/position.d.ts +47 -47
- package/margin/margin/position.js +10 -10
- package/margin/margin/router.d.ts +5 -5
- package/margin/margin/router.js +16 -16
- package/margin/margin/version.d.ts +1 -1
- package/margin/utils/index.js +22 -36
- package/package.json +1 -1
- package/parsers/apiReserveAssetDataEvent.js +4 -1
- package/parsers/lendingMarket.js +3 -4
- package/parsers/reserve.js +4 -1
- package/strategies.js +188 -204
- package/swap/quote.d.ts +1 -1
- package/swap/quote.js +28 -39
- package/swap/transaction.js +17 -26
- package/utils/events.js +20 -32
- package/utils/obligation.js +111 -135
- package/utils/simulate.d.ts +68 -1
- package/utils/simulate.js +149 -31
package/utils/simulate.d.ts
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { SuiPriceServiceConnection } from "@pythnetwork/pyth-sui-js";
|
|
1
|
+
import { PriceFeed, SuiPriceServiceConnection } from "@pythnetwork/pyth-sui-js";
|
|
2
2
|
import BigNumber from "bignumber.js";
|
|
3
3
|
import { Decimal } from "../_generated/suilend/decimal/structs";
|
|
4
4
|
import { PoolRewardManager, UserRewardManager } from "../_generated/suilend/liquidity-mining/structs";
|
|
@@ -22,6 +22,73 @@ export declare const calculateDepositAprPercent: (reserve: Reserve<string>) => B
|
|
|
22
22
|
export declare const compoundReserveInterest: (reserve: Reserve<string>, nowS: number) => Reserve<string>;
|
|
23
23
|
export declare const updatePoolRewardsManager: (manager: PoolRewardManager, nowMs: number) => PoolRewardManager;
|
|
24
24
|
export declare const refreshReservePrice: (reserves: Reserve<string>[], pythConnection: SuiPriceServiceConnection) => Promise<Reserve<string>[]>;
|
|
25
|
+
/**
|
|
26
|
+
* A connection that can report which of the requested feeds it could not serve,
|
|
27
|
+
* instead of failing the whole batch. Structural on purpose: any object with
|
|
28
|
+
* this method qualifies, so a caller can inject its own client without this
|
|
29
|
+
* package depending on it.
|
|
30
|
+
*/
|
|
31
|
+
export interface PartialPriceFeedSource {
|
|
32
|
+
getLatestPriceFeedsPartial(ids: string[]): Promise<{
|
|
33
|
+
feeds: PriceFeed[];
|
|
34
|
+
missing: string[];
|
|
35
|
+
}>;
|
|
36
|
+
}
|
|
37
|
+
/**
|
|
38
|
+
* Whether this connection can report WHICH requested feeds it could not serve.
|
|
39
|
+
*
|
|
40
|
+
* Exported because it is a precondition callers need to check, not just an
|
|
41
|
+
* internal branch: `tolerateMissingPriceFeeds` is meaningless without it (see
|
|
42
|
+
* `refreshReservesForInitialize`, which refuses the combination).
|
|
43
|
+
*/
|
|
44
|
+
export declare function canReportMissingPriceFeeds(connection: SuiPriceServiceConnection | PartialPriceFeedSource): connection is PartialPriceFeedSource;
|
|
45
|
+
/**
|
|
46
|
+
* The remedy both gap-tolerance failure paths point at.
|
|
47
|
+
*
|
|
48
|
+
* One constant because the two paths fail at different moments for the same
|
|
49
|
+
* underlying reason — `refreshReservesForInitialize` refuses up front on
|
|
50
|
+
* capability, `refreshReservePriceTolerant` only once an endpoint actually
|
|
51
|
+
* rejects a batch — and separately worded advice for one cause is how the two
|
|
52
|
+
* drift apart.
|
|
53
|
+
*/
|
|
54
|
+
export declare const PARTIAL_FETCH_REQUIRED_HINT = "inject a connection implementing getLatestPriceFeedsPartial (e.g. a failover connection that reports per-feed gaps)";
|
|
55
|
+
/**
|
|
56
|
+
* Like `refreshReservePrice`, but a reserve whose feed no endpoint can serve
|
|
57
|
+
* keeps the price already in its on-chain state instead of failing every other
|
|
58
|
+
* reserve with it.
|
|
59
|
+
*
|
|
60
|
+
* `refreshReservePrice` is all-or-nothing in both directions, and both are
|
|
61
|
+
* hazardous once an endpoint stops carrying every feed: a missing feed throws
|
|
62
|
+
* (so one retired market blinds pricing for the whole lending market), while an
|
|
63
|
+
* undefined response silently returns EVERY reserve at its stale price with no
|
|
64
|
+
* signal at all. Hermes v2 rejects an entire batch when any requested id is
|
|
65
|
+
* unknown, which makes both reachable in practice.
|
|
66
|
+
*
|
|
67
|
+
* This reports `unpricedCoinTypes` rather than deciding what an unpriceable
|
|
68
|
+
* reserve means — that judgement needs reserve config the caller has and this
|
|
69
|
+
* function should not second-guess. Nothing is ever fabricated here.
|
|
70
|
+
*
|
|
71
|
+
* TWO THINGS A CALLER MUST KNOW:
|
|
72
|
+
*
|
|
73
|
+
* 1. An unpriced reserve is returned in `reserves` carrying whatever price its
|
|
74
|
+
* on-chain state holds — which may be ZERO for a reserve that was never
|
|
75
|
+
* priced on-chain. Folding `reserves` into collateral or borrow-limit math
|
|
76
|
+
* without first excluding `unpricedCoinTypes` therefore prices that reserve
|
|
77
|
+
* at literal zero. `unpricedCoinTypes` is the only thing distinguishing a
|
|
78
|
+
* real price from a kept one; it is not optional to read.
|
|
79
|
+
* 2. Tolerance requires a connection that can REPORT a gap
|
|
80
|
+
* (`getLatestPriceFeedsPartial`). Handed a stock `SuiPriceServiceConnection`,
|
|
81
|
+
* this falls back to the all-or-nothing fetch, which Hermes v2 answers with
|
|
82
|
+
* a 404 for the whole batch when any id is unknown — so there is nothing
|
|
83
|
+
* left to be tolerant with. That fetch is kept because it is correct for an
|
|
84
|
+
* endpoint that OMITS unknown ids rather than rejecting the batch, but the
|
|
85
|
+
* failure is re-thrown with attribution rather than surfacing as a bare HTTP
|
|
86
|
+
* error. `refreshReservesForInitialize` rejects the combination outright.
|
|
87
|
+
*/
|
|
88
|
+
export declare const refreshReservePriceTolerant: (reserves: Reserve<string>[], pythConnection: SuiPriceServiceConnection | PartialPriceFeedSource) => Promise<{
|
|
89
|
+
reserves: Reserve<string>[];
|
|
90
|
+
unpricedCoinTypes: string[];
|
|
91
|
+
}>;
|
|
25
92
|
export declare const updateUserRewardManager: (poolManager: PoolRewardManager, userRewardManager: UserRewardManager, nowMs: number) => UserRewardManager;
|
|
26
93
|
export declare const refreshObligation: (unrefreshedObligation: Obligation<string>, refreshedReserves: Reserve<string>[]) => Obligation<string>;
|
|
27
94
|
export declare const numberToDecimal: (value: number) => Decimal;
|
package/utils/simulate.js
CHANGED
|
@@ -1,14 +1,5 @@
|
|
|
1
|
-
var __awaiter = (this && this.__awaiter) || function (thisArg, _arguments, P, generator) {
|
|
2
|
-
function adopt(value) { return value instanceof P ? value : new P(function (resolve) { resolve(value); }); }
|
|
3
|
-
return new (P || (P = Promise))(function (resolve, reject) {
|
|
4
|
-
function fulfilled(value) { try { step(generator.next(value)); } catch (e) { reject(e); } }
|
|
5
|
-
function rejected(value) { try { step(generator["throw"](value)); } catch (e) { reject(e); } }
|
|
6
|
-
function step(result) { result.done ? resolve(result.value) : adopt(result.value).then(fulfilled, rejected); }
|
|
7
|
-
step((generator = generator.apply(thisArg, _arguments || [])).next());
|
|
8
|
-
});
|
|
9
|
-
};
|
|
10
1
|
import { bcs } from "@mysten/sui/bcs";
|
|
11
|
-
import { toHex } from "@mysten/sui/utils";
|
|
2
|
+
import { normalizeStructTag, toHex } from "@mysten/sui/utils";
|
|
12
3
|
import BigNumber from "bignumber.js";
|
|
13
4
|
import { v4 as uuidv4 } from "uuid";
|
|
14
5
|
import { Decimal } from "../_generated/suilend/decimal/structs.js";
|
|
@@ -68,7 +59,6 @@ export const calculateDepositAprPercent = (reserve) => {
|
|
|
68
59
|
return calculateSupplyApr(reserve);
|
|
69
60
|
};
|
|
70
61
|
export const compoundReserveInterest = (reserve, nowS) => {
|
|
71
|
-
var _a, _b;
|
|
72
62
|
const timeElapsedSeconds = nowS - Number(reserve.interestLastUpdateTimestampS);
|
|
73
63
|
if (timeElapsedSeconds === 0) {
|
|
74
64
|
return reserve;
|
|
@@ -79,13 +69,13 @@ export const compoundReserveInterest = (reserve, nowS) => {
|
|
|
79
69
|
.div(100)
|
|
80
70
|
.div(365 * 24 * 60 * 60))
|
|
81
71
|
.pow(new BigNumber(timeElapsedSeconds).toNumber());
|
|
82
|
-
const updatedReserve =
|
|
72
|
+
const updatedReserve = { ...reserve };
|
|
83
73
|
const oldBorrowedAmount = decimalToBigNumber(reserve.borrowedAmount);
|
|
84
74
|
const oldCumulativeBorrowRate = decimalToBigNumber(reserve.cumulativeBorrowRate);
|
|
85
75
|
const oldUnclaimedSpreadFees = decimalToBigNumber(reserve.unclaimedSpreadFees);
|
|
86
76
|
updatedReserve.cumulativeBorrowRate = stringToDecimal(compoundedBorrowRate.multipliedBy(oldCumulativeBorrowRate).toString());
|
|
87
77
|
const netNewDebt = oldBorrowedAmount.multipliedBy(compoundedBorrowRate.minus(1));
|
|
88
|
-
const spreadFee = new BigNumber(
|
|
78
|
+
const spreadFee = new BigNumber(reserve.config.element?.spreadFeeBps?.toString() || 0);
|
|
89
79
|
updatedReserve.unclaimedSpreadFees = stringToDecimal(oldUnclaimedSpreadFees
|
|
90
80
|
.plus(netNewDebt.multipliedBy(spreadFee.dividedBy(10000)))
|
|
91
81
|
.toString());
|
|
@@ -96,7 +86,7 @@ export const compoundReserveInterest = (reserve, nowS) => {
|
|
|
96
86
|
return updatedReserve;
|
|
97
87
|
};
|
|
98
88
|
export const updatePoolRewardsManager = (manager, nowMs) => {
|
|
99
|
-
const updatedManager =
|
|
89
|
+
const updatedManager = { ...manager };
|
|
100
90
|
const timeElapsedMs = nowMs - Number(manager.lastUpdateTimeMs);
|
|
101
91
|
if (manager.totalShares === BigInt(0) || timeElapsedMs === 0) {
|
|
102
92
|
return updatedManager;
|
|
@@ -109,7 +99,7 @@ export const updatePoolRewardsManager = (manager, nowMs) => {
|
|
|
109
99
|
manager.lastUpdateTimeMs >= poolReward.endTimeMs) {
|
|
110
100
|
return poolReward;
|
|
111
101
|
}
|
|
112
|
-
const updatedPoolReward =
|
|
102
|
+
const updatedPoolReward = { ...poolReward };
|
|
113
103
|
const endTimeMs = Number(poolReward.endTimeMs);
|
|
114
104
|
const startTimeMs = Number(poolReward.startTimeMs);
|
|
115
105
|
const lastUpdateTimeMs = Number(manager.lastUpdateTimeMs);
|
|
@@ -128,27 +118,153 @@ export const updatePoolRewardsManager = (manager, nowMs) => {
|
|
|
128
118
|
updatedManager.lastUpdateTimeMs = BigInt(nowMs);
|
|
129
119
|
return updatedManager;
|
|
130
120
|
};
|
|
131
|
-
|
|
132
|
-
|
|
133
|
-
|
|
121
|
+
/** The Pyth feed id a reserve is priced from, in the response's `id` encoding. */
|
|
122
|
+
const reserveFeedId = (reserve) => toHex(new Uint8Array(reserve.priceIdentifier.bytes));
|
|
123
|
+
/**
|
|
124
|
+
* Index feeds by id for O(1) lookup, keeping the FIRST entry per id.
|
|
125
|
+
*
|
|
126
|
+
* First-wins matches the `priceFeeds.find(...)` this replaced, so a duplicated
|
|
127
|
+
* id in a response resolves to the same feed it always did. Pyth does not
|
|
128
|
+
* duplicate ids today; the point is that switching to a Map is not allowed to
|
|
129
|
+
* quietly change which one is picked if it ever does.
|
|
130
|
+
*/
|
|
131
|
+
const indexFeedsById = (priceFeeds) => {
|
|
132
|
+
const byId = new Map();
|
|
133
|
+
for (const feed of priceFeeds)
|
|
134
|
+
if (!byId.has(feed.id))
|
|
135
|
+
byId.set(feed.id, feed);
|
|
136
|
+
return byId;
|
|
137
|
+
};
|
|
138
|
+
/**
|
|
139
|
+
* Apply a feed's price to a COPY of `reserve`.
|
|
140
|
+
*
|
|
141
|
+
* Shared by both refresh functions so the price-mapping math has exactly one
|
|
142
|
+
* definition. It was duplicated verbatim, which meant a change to how a feed
|
|
143
|
+
* becomes a price had to be made twice or the two silently diverged.
|
|
144
|
+
*
|
|
145
|
+
* The two functions still own their own control flow — what a MISSING feed
|
|
146
|
+
* means is precisely where they are supposed to differ (see
|
|
147
|
+
* `refreshReservePriceTolerant`).
|
|
148
|
+
*/
|
|
149
|
+
const withFeedPrice = (reserve, priceFeed) => {
|
|
150
|
+
const newReserve = { ...reserve };
|
|
151
|
+
newReserve.price = stringToDecimal(priceFeed.getPriceUnchecked().getPriceAsNumberUnchecked().toString());
|
|
152
|
+
newReserve.smoothedPrice = stringToDecimal(priceFeed.getEmaPriceUnchecked().getPriceAsNumberUnchecked().toString());
|
|
153
|
+
newReserve.priceLastUpdateTimestampS = BigInt(priceFeed.getPriceUnchecked().publishTime);
|
|
154
|
+
return newReserve;
|
|
155
|
+
};
|
|
156
|
+
export const refreshReservePrice = async (reserves, pythConnection) => {
|
|
157
|
+
const priceIdentifiers = Array.from(new Set(reserves.map(reserveFeedId)));
|
|
158
|
+
const priceFeeds = await pythConnection.getLatestPriceFeeds(priceIdentifiers);
|
|
159
|
+
// Deliberately NOT a throw, and deliberately not expressed over the tolerant
|
|
160
|
+
// function below: consumers outside this repo depend on `undefined` meaning
|
|
161
|
+
// "keep every reserve's existing price". Hazardous (see the tolerant
|
|
162
|
+
// function's comment), but changing it here would turn a quiet failure loud
|
|
163
|
+
// under callers who never asked for that.
|
|
134
164
|
if (!priceFeeds)
|
|
135
165
|
return reserves;
|
|
166
|
+
const byId = indexFeedsById(priceFeeds);
|
|
136
167
|
const updatedReserves = [];
|
|
137
168
|
for (let i = 0; i < reserves.length; i++) {
|
|
138
169
|
const reserve = reserves[i];
|
|
139
|
-
const priceFeed =
|
|
170
|
+
const priceFeed = byId.get(reserveFeedId(reserve));
|
|
140
171
|
if (!priceFeed)
|
|
141
172
|
throw new Error(`Price feed not found for reserve ${reserve.coinType.name}`);
|
|
142
|
-
|
|
143
|
-
newReserve.price = stringToDecimal(priceFeed.getPriceUnchecked().getPriceAsNumberUnchecked().toString());
|
|
144
|
-
newReserve.smoothedPrice = stringToDecimal(priceFeed.getEmaPriceUnchecked().getPriceAsNumberUnchecked().toString());
|
|
145
|
-
newReserve.priceLastUpdateTimestampS = BigInt(priceFeed.getPriceUnchecked().publishTime);
|
|
146
|
-
updatedReserves.push(newReserve);
|
|
173
|
+
updatedReserves.push(withFeedPrice(reserve, priceFeed));
|
|
147
174
|
}
|
|
148
175
|
return updatedReserves;
|
|
149
|
-
}
|
|
176
|
+
};
|
|
177
|
+
/**
|
|
178
|
+
* Whether this connection can report WHICH requested feeds it could not serve.
|
|
179
|
+
*
|
|
180
|
+
* Exported because it is a precondition callers need to check, not just an
|
|
181
|
+
* internal branch: `tolerateMissingPriceFeeds` is meaningless without it (see
|
|
182
|
+
* `refreshReservesForInitialize`, which refuses the combination).
|
|
183
|
+
*/
|
|
184
|
+
export function canReportMissingPriceFeeds(connection) {
|
|
185
|
+
return (typeof connection.getLatestPriceFeedsPartial ===
|
|
186
|
+
"function");
|
|
187
|
+
}
|
|
188
|
+
/**
|
|
189
|
+
* The remedy both gap-tolerance failure paths point at.
|
|
190
|
+
*
|
|
191
|
+
* One constant because the two paths fail at different moments for the same
|
|
192
|
+
* underlying reason — `refreshReservesForInitialize` refuses up front on
|
|
193
|
+
* capability, `refreshReservePriceTolerant` only once an endpoint actually
|
|
194
|
+
* rejects a batch — and separately worded advice for one cause is how the two
|
|
195
|
+
* drift apart.
|
|
196
|
+
*/
|
|
197
|
+
export const PARTIAL_FETCH_REQUIRED_HINT = "inject a connection implementing getLatestPriceFeedsPartial (e.g. a failover connection that reports per-feed gaps)";
|
|
198
|
+
/**
|
|
199
|
+
* Like `refreshReservePrice`, but a reserve whose feed no endpoint can serve
|
|
200
|
+
* keeps the price already in its on-chain state instead of failing every other
|
|
201
|
+
* reserve with it.
|
|
202
|
+
*
|
|
203
|
+
* `refreshReservePrice` is all-or-nothing in both directions, and both are
|
|
204
|
+
* hazardous once an endpoint stops carrying every feed: a missing feed throws
|
|
205
|
+
* (so one retired market blinds pricing for the whole lending market), while an
|
|
206
|
+
* undefined response silently returns EVERY reserve at its stale price with no
|
|
207
|
+
* signal at all. Hermes v2 rejects an entire batch when any requested id is
|
|
208
|
+
* unknown, which makes both reachable in practice.
|
|
209
|
+
*
|
|
210
|
+
* This reports `unpricedCoinTypes` rather than deciding what an unpriceable
|
|
211
|
+
* reserve means — that judgement needs reserve config the caller has and this
|
|
212
|
+
* function should not second-guess. Nothing is ever fabricated here.
|
|
213
|
+
*
|
|
214
|
+
* TWO THINGS A CALLER MUST KNOW:
|
|
215
|
+
*
|
|
216
|
+
* 1. An unpriced reserve is returned in `reserves` carrying whatever price its
|
|
217
|
+
* on-chain state holds — which may be ZERO for a reserve that was never
|
|
218
|
+
* priced on-chain. Folding `reserves` into collateral or borrow-limit math
|
|
219
|
+
* without first excluding `unpricedCoinTypes` therefore prices that reserve
|
|
220
|
+
* at literal zero. `unpricedCoinTypes` is the only thing distinguishing a
|
|
221
|
+
* real price from a kept one; it is not optional to read.
|
|
222
|
+
* 2. Tolerance requires a connection that can REPORT a gap
|
|
223
|
+
* (`getLatestPriceFeedsPartial`). Handed a stock `SuiPriceServiceConnection`,
|
|
224
|
+
* this falls back to the all-or-nothing fetch, which Hermes v2 answers with
|
|
225
|
+
* a 404 for the whole batch when any id is unknown — so there is nothing
|
|
226
|
+
* left to be tolerant with. That fetch is kept because it is correct for an
|
|
227
|
+
* endpoint that OMITS unknown ids rather than rejecting the batch, but the
|
|
228
|
+
* failure is re-thrown with attribution rather than surfacing as a bare HTTP
|
|
229
|
+
* error. `refreshReservesForInitialize` rejects the combination outright.
|
|
230
|
+
*/
|
|
231
|
+
export const refreshReservePriceTolerant = async (reserves, pythConnection) => {
|
|
232
|
+
const priceIdentifiers = Array.from(new Set(reserves.map(reserveFeedId)));
|
|
233
|
+
let priceFeeds;
|
|
234
|
+
if (canReportMissingPriceFeeds(pythConnection)) {
|
|
235
|
+
priceFeeds = (await pythConnection.getLatestPriceFeedsPartial(priceIdentifiers)).feeds;
|
|
236
|
+
}
|
|
237
|
+
else {
|
|
238
|
+
try {
|
|
239
|
+
priceFeeds = await pythConnection.getLatestPriceFeeds(priceIdentifiers);
|
|
240
|
+
}
|
|
241
|
+
catch (error) {
|
|
242
|
+
// Not swallowed — a transport failure must stay a failure, or every
|
|
243
|
+
// reserve silently reports as unpriced and the caller cannot tell a dead
|
|
244
|
+
// endpoint from a retired feed. Re-thrown only to name the cause, which a
|
|
245
|
+
// bare "HTTP error! status: 404" does not.
|
|
246
|
+
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 });
|
|
247
|
+
}
|
|
248
|
+
}
|
|
249
|
+
const byId = indexFeedsById(priceFeeds ?? []);
|
|
250
|
+
const unpricedCoinTypes = [];
|
|
251
|
+
const updatedReserves = reserves.map((reserve) => {
|
|
252
|
+
const priceFeed = byId.get(reserveFeedId(reserve));
|
|
253
|
+
if (!priceFeed) {
|
|
254
|
+
// Normalized to match every other coin type this SDK returns
|
|
255
|
+
// (`reserveMap`, `reserveCoinTypes`, and the parsers are all
|
|
256
|
+
// normalizeStructTag'd). The raw on-chain form never compares equal to
|
|
257
|
+
// one of those keys — it lacks the `0x` prefix — so reporting it raw made
|
|
258
|
+
// the cross-reference this field exists for silently never match.
|
|
259
|
+
unpricedCoinTypes.push(normalizeStructTag(reserve.coinType.name));
|
|
260
|
+
return reserve;
|
|
261
|
+
}
|
|
262
|
+
return withFeedPrice(reserve, priceFeed);
|
|
263
|
+
});
|
|
264
|
+
return { reserves: updatedReserves, unpricedCoinTypes };
|
|
265
|
+
};
|
|
150
266
|
export const updateUserRewardManager = (poolManager, userRewardManager, nowMs) => {
|
|
151
|
-
const updatedUserRewardManager =
|
|
267
|
+
const updatedUserRewardManager = { ...userRewardManager };
|
|
152
268
|
for (let i = 0; i < poolManager.poolRewards.length; i++) {
|
|
153
269
|
const poolReward = poolManager.poolRewards[i];
|
|
154
270
|
if (poolReward == null) {
|
|
@@ -158,7 +274,7 @@ export const updateUserRewardManager = (poolManager, userRewardManager, nowMs) =
|
|
|
158
274
|
userRewardManager.rewards.push(null);
|
|
159
275
|
}
|
|
160
276
|
const oldReward = updatedUserRewardManager.rewards[i];
|
|
161
|
-
const reward =
|
|
277
|
+
const reward = { ...oldReward };
|
|
162
278
|
if (oldReward != null) {
|
|
163
279
|
const newRewards = decimalToBigNumber(poolReward.cumulativeRewardsPerShare)
|
|
164
280
|
.minus(decimalToBigNumber(oldReward.cumulativeRewardsPerShare))
|
|
@@ -183,13 +299,13 @@ export const updateUserRewardManager = (poolManager, userRewardManager, nowMs) =
|
|
|
183
299
|
return updatedUserRewardManager;
|
|
184
300
|
};
|
|
185
301
|
export const refreshObligation = (unrefreshedObligation, refreshedReserves) => {
|
|
186
|
-
const obligation =
|
|
302
|
+
const obligation = { ...unrefreshedObligation };
|
|
187
303
|
// Refresh Deposits
|
|
188
304
|
let depositValueUsd = new BigNumber(0);
|
|
189
305
|
let allowedBorrowValueUsd = new BigNumber(0);
|
|
190
306
|
let unhealthyBorrowValueUsd = new BigNumber(0);
|
|
191
307
|
for (let i = 0; i < obligation.deposits.length; i++) {
|
|
192
|
-
const deposit =
|
|
308
|
+
const deposit = { ...obligation.deposits[i] };
|
|
193
309
|
const reserve = refreshedReserves.find((r) => r.coinType.name === deposit.coinType.name);
|
|
194
310
|
if (!reserve) {
|
|
195
311
|
throw new Error(`Unable to find reserve for coin type ${deposit.coinType.name}`);
|
|
@@ -219,7 +335,9 @@ export const refreshObligation = (unrefreshedObligation, refreshedReserves) => {
|
|
|
219
335
|
throw new Error(`Unable to find reserve for coin type ${unrefreshedBorrow.coinType.name}`);
|
|
220
336
|
}
|
|
221
337
|
const config = reserve.config.element;
|
|
222
|
-
const borrow =
|
|
338
|
+
const borrow = {
|
|
339
|
+
...compoundDebt(unrefreshedBorrow, reserve),
|
|
340
|
+
};
|
|
223
341
|
obligation.userRewardManagers[Number(borrow.userRewardManagerIndex)] =
|
|
224
342
|
updateUserRewardManager(reserve.borrowsPoolRewardManager, obligation.userRewardManagers[Number(borrow.userRewardManagerIndex)], Date.now());
|
|
225
343
|
const marketValue = decimalToBigNumber(borrow.borrowedAmount)
|
|
@@ -276,7 +394,7 @@ export const totalSupply = (reserve) => {
|
|
|
276
394
|
.minus(decimalToBigNumber(reserve.unclaimedSpreadFees));
|
|
277
395
|
};
|
|
278
396
|
export const compoundDebt = (borrow, reserve) => {
|
|
279
|
-
const borrowCopy =
|
|
397
|
+
const borrowCopy = { ...borrow };
|
|
280
398
|
const newCumulativeBorrowRate = decimalToBigNumber(reserve.cumulativeBorrowRate);
|
|
281
399
|
const compoundedInterestRate = newCumulativeBorrowRate.dividedBy(decimalToBigNumber(borrow.cumulativeBorrowRate));
|
|
282
400
|
borrowCopy.borrowedAmount = stringToDecimal(decimalToBigNumber(borrow.borrowedAmount)
|