@suilend/sdk 14.2.0-next.ge5d3397 → 14.2.0-next.gf80403d
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 +2 -2
- package/lib/initialize.js +7 -7
- package/package.json +1 -1
- package/utils/simulate.d.ts +10 -4
- package/utils/simulate.js +20 -7
package/lib/initialize.d.ts
CHANGED
|
@@ -31,7 +31,7 @@ export declare const resolveCoinMetadataForInitialize: (miscCoinTypes: string[],
|
|
|
31
31
|
reserveCoinMetadataMap: Record<string, import("@suilend/sui-core").Token>;
|
|
32
32
|
rewardCoinMetadataMap: Record<string, import("@suilend/sui-core").Token>;
|
|
33
33
|
}>;
|
|
34
|
-
export declare const refreshReservesForInitializeAll: (reservesByMarket: Reserve<string>[][], pythConnection: simulate.PriceFeedSource, tolerateMissingPriceFeeds?: boolean) => Promise<{
|
|
34
|
+
export declare const refreshReservesForInitializeAll: (reservesByMarket: Reserve<string>[][], pythConnection: simulate.PriceFeedSource, tolerateMissingPriceFeeds?: boolean, marketVersions?: bigint[]) => Promise<{
|
|
35
35
|
reserves: Reserve<string>[];
|
|
36
36
|
unpricedCoinTypes: string[];
|
|
37
37
|
}[]>;
|
|
@@ -42,7 +42,7 @@ export declare const refreshReservesForInitializeAll: (reservesByMarket: Reserve
|
|
|
42
42
|
* them is a throw, which is the whole reason the flag exists and is exactly
|
|
43
43
|
* what a regression here would silently change.
|
|
44
44
|
*/
|
|
45
|
-
export declare const refreshReservesForInitialize: (reserves: Reserve<string>[], pythConnection: simulate.PriceFeedSource, tolerateMissingPriceFeeds?: boolean) => Promise<{
|
|
45
|
+
export declare const refreshReservesForInitialize: (reserves: Reserve<string>[], pythConnection: simulate.PriceFeedSource, tolerateMissingPriceFeeds?: boolean, marketVersion?: bigint) => Promise<{
|
|
46
46
|
reserves: Reserve<string>[];
|
|
47
47
|
unpricedCoinTypes: string[];
|
|
48
48
|
}>;
|
package/lib/initialize.js
CHANGED
|
@@ -162,7 +162,7 @@ export const resolveCoinMetadataForInitialize = async (miscCoinTypes, reserveCoi
|
|
|
162
162
|
]);
|
|
163
163
|
return resolved;
|
|
164
164
|
};
|
|
165
|
-
export const refreshReservesForInitializeAll = async (reservesByMarket, pythConnection, tolerateMissingPriceFeeds) => {
|
|
165
|
+
export const refreshReservesForInitializeAll = async (reservesByMarket, pythConnection, tolerateMissingPriceFeeds, marketVersions) => {
|
|
166
166
|
const allReserves = reservesByMarket.flat();
|
|
167
167
|
if (tolerateMissingPriceFeeds) {
|
|
168
168
|
// Tolerance is only expressible over a connection that can report WHICH
|
|
@@ -184,13 +184,13 @@ export const refreshReservesForInitializeAll = async (reservesByMarket, pythConn
|
|
|
184
184
|
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.`);
|
|
185
185
|
}
|
|
186
186
|
const priceFeeds = await simulate.fetchReservePriceFeedsTolerant(allReserves, pythConnection);
|
|
187
|
-
return reservesByMarket.map((reserves) => simulate.applyReservePriceFeedsTolerant(reserves, priceFeeds));
|
|
187
|
+
return reservesByMarket.map((reserves, i) => simulate.applyReservePriceFeedsTolerant(reserves, priceFeeds, marketVersions?.[i]));
|
|
188
188
|
}
|
|
189
189
|
// Default path, behaviourally identical to what this function did before the
|
|
190
190
|
// flag existed: throws if any requested feed is absent from the response.
|
|
191
191
|
const priceFeeds = await pythConnection.getLatestPriceFeeds(simulate.reservePriceFeedIds(allReserves));
|
|
192
|
-
return reservesByMarket.map((reserves) => ({
|
|
193
|
-
reserves: simulate.applyReservePriceFeeds(reserves, priceFeeds),
|
|
192
|
+
return reservesByMarket.map((reserves, i) => ({
|
|
193
|
+
reserves: simulate.applyReservePriceFeeds(reserves, priceFeeds, marketVersions?.[i]),
|
|
194
194
|
unpricedCoinTypes: [],
|
|
195
195
|
}));
|
|
196
196
|
};
|
|
@@ -201,8 +201,8 @@ export const refreshReservesForInitializeAll = async (reservesByMarket, pythConn
|
|
|
201
201
|
* them is a throw, which is the whole reason the flag exists and is exactly
|
|
202
202
|
* what a regression here would silently change.
|
|
203
203
|
*/
|
|
204
|
-
export const refreshReservesForInitialize = async (reserves, pythConnection, tolerateMissingPriceFeeds) => {
|
|
205
|
-
const [refreshed] = await refreshReservesForInitializeAll([reserves], pythConnection, tolerateMissingPriceFeeds);
|
|
204
|
+
export const refreshReservesForInitialize = async (reserves, pythConnection, tolerateMissingPriceFeeds, marketVersion) => {
|
|
205
|
+
const [refreshed] = await refreshReservesForInitializeAll([reserves], pythConnection, tolerateMissingPriceFeeds, marketVersion === undefined ? undefined : [marketVersion]);
|
|
206
206
|
return refreshed;
|
|
207
207
|
};
|
|
208
208
|
/**
|
|
@@ -360,7 +360,7 @@ const initializeSuilendMarkets = async (suiGrpcClient, markets, fallbackPythEndp
|
|
|
360
360
|
const reservelessActiveRewardCoinTypesByMarket = coinTypesByMarket.map(reservelessActiveRewardCoinTypes);
|
|
361
361
|
const [[refreshedWithoutTemporaryByMarket, fabricatedPriceCoinTypesByMarket], coinMetadataByMarket, rewardUsdPrices, kBpsByMarket,] = await Promise.all([
|
|
362
362
|
Promise.all([
|
|
363
|
-
resolvePythConnection(options?.pythConnection, fallbackPythEndpoint, options?.pythConnectionConfig).then((pythConnection) => refreshReservesForInitializeAll(reservesByMarket.map((r) => r.withoutTemporaryPythPriceFeeds), pythConnection, options?.tolerateMissingPriceFeeds)),
|
|
363
|
+
resolvePythConnection(options?.pythConnection, fallbackPythEndpoint, options?.pythConnectionConfig).then((pythConnection) => refreshReservesForInitializeAll(reservesByMarket.map((r) => r.withoutTemporaryPythPriceFeeds), pythConnection, options?.tolerateMissingPriceFeeds, markets.map(({ suilendClient }) => suilendClient.lendingMarket.version))),
|
|
364
364
|
priceTemporaryPythFeedReservesAll(reservesByMarket.map((r) => r.withTemporaryPythPriceFeeds), fetchPrices),
|
|
365
365
|
]),
|
|
366
366
|
resolveCoinMetadataForInitializeAll(coinTypesByMarket.map(({ uniqueMiscCoinTypes, uniqueReserveCoinTypes, uniqueRewardCoinTypes, }) => ({
|
package/package.json
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"name":"@suilend/sdk","version":"14.2.0-next.
|
|
1
|
+
{"name":"@suilend/sdk","version":"14.2.0-next.gf80403d","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/interpolate":"./utils/interpolate.js","./utils/obligation":"./utils/obligation.js","./utils/priceGuard":"./utils/priceGuard.js","./utils/simulate":"./utils/simulate.js","./_generated/_envs":"./_generated/_envs/index.js","./_generated/_envs/mainnet":"./_generated/_envs/mainnet.js","./_generated/_framework/env":"./_generated/_framework/env.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/pyth-2/price-identifier/structs":"./_generated/_dependencies/pyth-2/price-identifier/structs.js","./_generated/_dependencies/std/ascii/structs":"./_generated/_dependencies/std/ascii/structs.js","./_generated/_dependencies/std/option/structs":"./_generated/_dependencies/std/option/structs.js","./_generated/_dependencies/std/type-name/structs":"./_generated/_dependencies/std/type-name/structs.js","./_generated/_dependencies/sui/bag/structs":"./_generated/_dependencies/sui/bag/structs.js","./_generated/_dependencies/sui/balance/structs":"./_generated/_dependencies/sui/balance/structs.js","./_generated/_dependencies/sui/object/structs":"./_generated/_dependencies/sui/object/structs.js","./_generated/_dependencies/sui/object-table/structs":"./_generated/_dependencies/sui/object-table/structs.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"},"types":"./index.d.ts","scripts":{"codegen":"node ./codegen.js","codegen:install":"node ./install-codegen.js","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":{"@cetusprotocol/aggregator-sdk":"1.7.3","@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.3","uuid":"14.0.2"},"devDependencies":{"@mysten/bcs":"2.1.2","@mysten/sui":"2.35.0","@suilend/sui-core":"2.2.0","@tsconfig/recommended":"1.0.13","@types/bn.js":"5.2.0","@types/lodash":"4.17.26","@types/node":"26.6.5","fast-check":"4.10.2","prettier":"3.9.10","ts-node":"10.9.2","typescript":"6.0.3","vite":"8.3.4","vitest":"5.0.4"},"peerDependencies":{"@mysten/bcs":"^2.1.1","@mysten/sui":">=2.31.3 <3","@suilend/sui-core":"^2.0.1"},"type":"module"}
|
package/utils/simulate.d.ts
CHANGED
|
@@ -56,9 +56,15 @@ export interface PriceFeedSource {
|
|
|
56
56
|
getLatestPriceFeeds(ids: string[]): Promise<FeedPrice[] | undefined>;
|
|
57
57
|
}
|
|
58
58
|
export { normalizeFeedId };
|
|
59
|
+
/**
|
|
60
|
+
* The first lending market version whose refresh aborts `EInvalidPrice` on a
|
|
61
|
+
* spot that parses to a zero `Decimal`, instead of storing it. Earlier versions
|
|
62
|
+
* store the zero, so the model follows the market's own version.
|
|
63
|
+
*/
|
|
64
|
+
export declare const ZERO_PRICE_ABORT_MARKET_VERSION: bigint;
|
|
59
65
|
export declare const reservePriceFeedIds: (reserves: Reserve<string>[]) => string[];
|
|
60
|
-
export declare const applyReservePriceFeeds: (reserves: Reserve<string>[], priceFeeds: FeedPrice[] | undefined) => Reserve<string>[];
|
|
61
|
-
export declare const refreshReservePrice: (reserves: Reserve<string>[], pythConnection: PriceFeedSource) => Promise<Reserve<string>[]>;
|
|
66
|
+
export declare const applyReservePriceFeeds: (reserves: Reserve<string>[], priceFeeds: FeedPrice[] | undefined, marketVersion?: bigint) => Reserve<string>[];
|
|
67
|
+
export declare const refreshReservePrice: (reserves: Reserve<string>[], pythConnection: PriceFeedSource, marketVersion?: bigint) => Promise<Reserve<string>[]>;
|
|
62
68
|
/**
|
|
63
69
|
* A connection that can report which of the requested feeds it could not serve,
|
|
64
70
|
* instead of failing the whole batch. Structural on purpose: any object with
|
|
@@ -123,11 +129,11 @@ export declare const PARTIAL_FETCH_REQUIRED_HINT = "inject a connection implemen
|
|
|
123
129
|
* error. `refreshReservesForInitialize` rejects the combination outright.
|
|
124
130
|
*/
|
|
125
131
|
export declare const fetchReservePriceFeedsTolerant: (reserves: Reserve<string>[], pythConnection: PriceFeedSource | PartialPriceFeedSource) => Promise<FeedPrice[] | undefined>;
|
|
126
|
-
export declare const applyReservePriceFeedsTolerant: (reserves: Reserve<string>[], priceFeeds: FeedPrice[] | undefined) => {
|
|
132
|
+
export declare const applyReservePriceFeedsTolerant: (reserves: Reserve<string>[], priceFeeds: FeedPrice[] | undefined, marketVersion?: bigint) => {
|
|
127
133
|
reserves: Reserve<string>[];
|
|
128
134
|
unpricedCoinTypes: string[];
|
|
129
135
|
};
|
|
130
|
-
export declare const refreshReservePriceTolerant: (reserves: Reserve<string>[], pythConnection: PriceFeedSource | PartialPriceFeedSource) => Promise<{
|
|
136
|
+
export declare const refreshReservePriceTolerant: (reserves: Reserve<string>[], pythConnection: PriceFeedSource | PartialPriceFeedSource, marketVersion?: bigint) => Promise<{
|
|
131
137
|
reserves: Reserve<string>[];
|
|
132
138
|
unpricedCoinTypes: string[];
|
|
133
139
|
}>;
|
package/utils/simulate.js
CHANGED
|
@@ -180,8 +180,17 @@ const withFeedPrice = (reserve, priceFeed) => {
|
|
|
180
180
|
newReserve.unclampedPrices = undefined;
|
|
181
181
|
return newReserve;
|
|
182
182
|
};
|
|
183
|
+
/**
|
|
184
|
+
* The first lending market version whose refresh aborts `EInvalidPrice` on a
|
|
185
|
+
* spot that parses to a zero `Decimal`, instead of storing it. Earlier versions
|
|
186
|
+
* store the zero, so the model follows the market's own version.
|
|
187
|
+
*/
|
|
188
|
+
export const ZERO_PRICE_ABORT_MARKET_VERSION = BigInt(8);
|
|
189
|
+
const refreshAbortsOnZeroPrice = (marketVersion, refreshed) => marketVersion !== undefined &&
|
|
190
|
+
marketVersion >= ZERO_PRICE_ABORT_MARKET_VERSION &&
|
|
191
|
+
refreshed.price.value === BigInt(0);
|
|
183
192
|
export const reservePriceFeedIds = (reserves) => Array.from(new Set(reserves.map(reserveFeedId)));
|
|
184
|
-
export const applyReservePriceFeeds = (reserves, priceFeeds) => {
|
|
193
|
+
export const applyReservePriceFeeds = (reserves, priceFeeds, marketVersion) => {
|
|
185
194
|
// Deliberately NOT a throw, and deliberately not expressed over the tolerant
|
|
186
195
|
// function below: consumers outside this repo depend on `undefined` meaning
|
|
187
196
|
// "keep every reserve's existing price". Hazardous (see the tolerant
|
|
@@ -194,10 +203,13 @@ export const applyReservePriceFeeds = (reserves, priceFeeds) => {
|
|
|
194
203
|
const priceFeed = byId.get(reserveFeedId(reserve));
|
|
195
204
|
if (!priceFeed)
|
|
196
205
|
throw new Error(`Price feed not found for reserve ${reserve.coinType}`);
|
|
197
|
-
|
|
206
|
+
const refreshed = withFeedPrice(reserve, priceFeed);
|
|
207
|
+
if (refreshAbortsOnZeroPrice(marketVersion, refreshed))
|
|
208
|
+
throw new Error(`Price feed for reserve ${reserve.coinType} reads a zero price, which a market at version ${marketVersion} rejects with EInvalidPrice`);
|
|
209
|
+
return refreshed;
|
|
198
210
|
});
|
|
199
211
|
};
|
|
200
|
-
export const refreshReservePrice = async (reserves, pythConnection) => applyReservePriceFeeds(reserves, await pythConnection.getLatestPriceFeeds(reservePriceFeedIds(reserves)));
|
|
212
|
+
export const refreshReservePrice = async (reserves, pythConnection, marketVersion) => applyReservePriceFeeds(reserves, await pythConnection.getLatestPriceFeeds(reservePriceFeedIds(reserves)), marketVersion);
|
|
201
213
|
/**
|
|
202
214
|
* Whether this connection can report WHICH requested feeds it could not serve.
|
|
203
215
|
*
|
|
@@ -269,12 +281,13 @@ export const fetchReservePriceFeedsTolerant = async (reserves, pythConnection) =
|
|
|
269
281
|
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 });
|
|
270
282
|
}
|
|
271
283
|
};
|
|
272
|
-
export const applyReservePriceFeedsTolerant = (reserves, priceFeeds) => {
|
|
284
|
+
export const applyReservePriceFeedsTolerant = (reserves, priceFeeds, marketVersion) => {
|
|
273
285
|
const byId = indexFeedsById(priceFeeds ?? []);
|
|
274
286
|
const unpricedCoinTypes = [];
|
|
275
287
|
const updatedReserves = reserves.map((reserve) => {
|
|
276
288
|
const priceFeed = byId.get(reserveFeedId(reserve));
|
|
277
|
-
|
|
289
|
+
const refreshed = priceFeed && withFeedPrice(reserve, priceFeed);
|
|
290
|
+
if (!refreshed || refreshAbortsOnZeroPrice(marketVersion, refreshed)) {
|
|
278
291
|
// Normalized to match every other coin type this SDK returns
|
|
279
292
|
// (`reserveMap`, `reserveCoinTypes`, and the parsers are all
|
|
280
293
|
// normalizeStructTag'd). The raw on-chain form never compares equal to
|
|
@@ -283,11 +296,11 @@ export const applyReservePriceFeedsTolerant = (reserves, priceFeeds) => {
|
|
|
283
296
|
unpricedCoinTypes.push(normalizeStructTag(reserve.coinType));
|
|
284
297
|
return reserve;
|
|
285
298
|
}
|
|
286
|
-
return
|
|
299
|
+
return refreshed;
|
|
287
300
|
});
|
|
288
301
|
return { reserves: updatedReserves, unpricedCoinTypes };
|
|
289
302
|
};
|
|
290
|
-
export const refreshReservePriceTolerant = async (reserves, pythConnection) => applyReservePriceFeedsTolerant(reserves, await fetchReservePriceFeedsTolerant(reserves, pythConnection));
|
|
303
|
+
export const refreshReservePriceTolerant = async (reserves, pythConnection, marketVersion) => applyReservePriceFeedsTolerant(reserves, await fetchReservePriceFeedsTolerant(reserves, pythConnection), marketVersion);
|
|
291
304
|
const warnedStaleRewardSnapshots = new Set();
|
|
292
305
|
const warnStaleRewardSnapshot = (poolRewardId, poolCumulative, userCumulative) => {
|
|
293
306
|
if (warnedStaleRewardSnapshots.has(poolRewardId))
|