@suilend/sdk 6.0.3 → 7.0.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/client.js +11 -13
- package/package.json +1 -1
- package/utils/obligation.d.ts +1 -0
- package/utils/simulate.d.ts +64 -1
- package/utils/simulate.js +131 -40
- package/lib/pythAdapter.d.ts +0 -74
- package/lib/pythAdapter.js +0 -182
package/client.js
CHANGED
|
@@ -115,6 +115,10 @@ export async function listAllOwnedObjects(suiGrpcClient, owner, type, include) {
|
|
|
115
115
|
const objs = await suiGrpcClient.listOwnedObjects({
|
|
116
116
|
owner,
|
|
117
117
|
cursor,
|
|
118
|
+
// The server defaults to 50/page and caps at 1000; this walk runs to
|
|
119
|
+
// exhaustion with no retry, so the default multiplied round-trips
|
|
120
|
+
// (and per-page transient-failure exposure) ~15x for no reason.
|
|
121
|
+
limit: 1000,
|
|
118
122
|
type,
|
|
119
123
|
include,
|
|
120
124
|
});
|
|
@@ -135,24 +139,18 @@ export class SuilendClient {
|
|
|
135
139
|
constructor(lendingMarket, suiGrpcClient) {
|
|
136
140
|
this.lendingMarket = lendingMarket;
|
|
137
141
|
this.suiGrpcClient = suiGrpcClient;
|
|
138
|
-
// The grpc client goes in DIRECTLY
|
|
142
|
+
// The grpc client goes in DIRECTLY. `SuiGrpcClient` satisfies v4's
|
|
143
|
+
// `ClientWithCoreApi` natively, so nothing needs adapting.
|
|
139
144
|
//
|
|
140
145
|
// pyth v4's SuiPythClient reads through `provider.core.getObject` and
|
|
141
146
|
// `provider.core.getDynamicField`; v2 read the legacy top-level
|
|
142
147
|
// `provider.getObject` / `getDynamicFieldObject`. The class name survived the
|
|
143
|
-
// major bump, its provider contract did not — so the
|
|
144
|
-
//
|
|
145
|
-
// pythClient throw "Cannot read properties of undefined (reading
|
|
148
|
+
// major bump, its provider contract did not — so wrapping the client in
|
|
149
|
+
// anything that presents the legacy shape without a `.core` makes every call
|
|
150
|
+
// through pythClient throw "Cannot read properties of undefined (reading
|
|
146
151
|
// 'getDynamicField')". That is the on-chain price-update path, i.e. every
|
|
147
|
-
// deposit/withdraw/borrow/repay, and nothing read-only touches it
|
|
148
|
-
//
|
|
149
|
-
// `SuiGrpcClient` satisfies v4's `ClientWithCoreApi` natively, so no adapter
|
|
150
|
-
// is needed here at all. The adapter's hardcoded price-table fallback is also
|
|
151
|
-
// obsolete: `core.getDynamicField` resolves the pyth `price_info` wrapper over
|
|
152
|
-
// grpc and returns the same table id the adapter had pinned.
|
|
153
|
-
//
|
|
154
|
-
// createJsonRpcAdapter is still exported — the indexer's bluefin swapper
|
|
155
|
-
// needs the legacy shape — it just must not be used for pyth v4.
|
|
152
|
+
// deposit/withdraw/borrow/repay, and nothing read-only touches it, so a
|
|
153
|
+
// typecheck and every page-load check stay green while writes are broken.
|
|
156
154
|
this.pythClient = new SuiPythClient(suiGrpcClient, PYTH_STATE_ID, WORMHOLE_STATE_ID);
|
|
157
155
|
const pythEndpoint = PRIMARY_PYTH_ENDPOINT; // TODO: Use getWorkingPythEndpoint
|
|
158
156
|
this.pythConnection = new SuiPriceServiceConnection(pythEndpoint, {
|
package/package.json
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"name":"@suilend/sdk","version":"
|
|
1
|
+
{"name":"@suilend/sdk","version":"7.0.1","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/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/i64/structs":"./_generated/_dependencies/source/0x8d97f1cd6ac663735be08d1d2b6d02a159e711586461306ce60a2b7a6a565a9e/i64/structs.js","./_generated/_dependencies/source/0x8d97f1cd6ac663735be08d1d2b6d02a159e711586461306ce60a2b7a6a565a9e/price/structs":"./_generated/_dependencies/source/0x8d97f1cd6ac663735be08d1d2b6d02a159e711586461306ce60a2b7a6a565a9e/price/structs.js","./_generated/_dependencies/source/0x8d97f1cd6ac663735be08d1d2b6d02a159e711586461306ce60a2b7a6a565a9e/price-feed/structs":"./_generated/_dependencies/source/0x8d97f1cd6ac663735be08d1d2b6d02a159e711586461306ce60a2b7a6a565a9e/price-feed/structs.js","./_generated/_dependencies/source/0x8d97f1cd6ac663735be08d1d2b6d02a159e711586461306ce60a2b7a6a565a9e/price-identifier/structs":"./_generated/_dependencies/source/0x8d97f1cd6ac663735be08d1d2b6d02a159e711586461306ce60a2b7a6a565a9e/price-identifier/structs.js","./_generated/_dependencies/source/0x8d97f1cd6ac663735be08d1d2b6d02a159e711586461306ce60a2b7a6a565a9e/price-info/structs":"./_generated/_dependencies/source/0x8d97f1cd6ac663735be08d1d2b6d02a159e711586461306ce60a2b7a6a565a9e/price-info/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.5.0","@cetusprotocol/aggregator-sdk":"^1.5.7","@flowx-finance/sdk":"^2.1.0","@pythnetwork/hermes-client":"3.1.0","@pythnetwork/pyth-sui-js":"4.0.0","@suilend/springsui-sdk":"^4.0.0","bignumber.js":"^11.1.5","bn.js":"^5.2.2","crypto-js":"^4.2.0","lodash":"^4.17.21","p-limit":"7.3.1","uuid":"^14.0.1"},"devDependencies":{"@mysten/bcs":"^2.0.5","@mysten/sui":"2.23.1","@suilend/sui-core":"^1.0.0","@tsconfig/recommended":"^1.0.8","@types/bn.js":"^5.2.0","@types/lodash":"^4.17.20","@types/node":"^26.1.2","fast-check":"^4.9.0","prettier":"^3.3.3","ts-node":"^10.9.2","typescript":"^6.0.3","vitest":"4.1.10"},"peerDependencies":{"@mysten/bcs":"^2.0.5","@mysten/sui":">=2.22.1 <3","@suilend/sui-core":"^1.0.0"},"type":"module"}
|
package/utils/obligation.d.ts
CHANGED
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
import { SuiClientTypes } from "@mysten/sui/client";
|
|
2
2
|
import { SuiGraphQLClient } from "@mysten/sui/graphql";
|
|
3
3
|
import { SuiGrpcClient } from "@mysten/sui/grpc";
|
|
4
|
+
import { BigNumber } from "bignumber.js";
|
|
4
5
|
import { Obligation } from "../_generated/suilend/obligation/structs";
|
|
5
6
|
import { ParsedObligation } from "../parsers";
|
|
6
7
|
export declare function fetchAllObligationsForMarketWithHandler(suiGrpcClient: SuiGrpcClient, lendingMarketId: string, lendingMarketType: string, chunkHandler: (obligations: Obligation<string>[]) => Promise<void>): Promise<void>;
|
package/utils/simulate.d.ts
CHANGED
|
@@ -1,8 +1,9 @@
|
|
|
1
1
|
import BigNumber from "bignumber.js";
|
|
2
2
|
import { Decimal } from "../_generated/suilend/decimal/structs";
|
|
3
3
|
import { PoolRewardManager, UserRewardManager } from "../_generated/suilend/liquidity-mining/structs";
|
|
4
|
-
import { Borrow, Obligation } from "../_generated/suilend/obligation/structs";
|
|
4
|
+
import { Borrow, Deposit, Obligation } from "../_generated/suilend/obligation/structs";
|
|
5
5
|
import { Reserve } from "../_generated/suilend/reserve/structs";
|
|
6
|
+
import { ReserveConfig } from "../_generated/suilend/reserve-config/structs";
|
|
6
7
|
import { normalizeFeedId } from "./feedId";
|
|
7
8
|
/**
|
|
8
9
|
* @deprecated since version 1.0.8. Use `calculateUtilizationPercent` instead.
|
|
@@ -113,6 +114,30 @@ export declare const refreshReservePriceTolerant: (reserves: Reserve<string>[],
|
|
|
113
114
|
unpricedCoinTypes: string[];
|
|
114
115
|
}>;
|
|
115
116
|
export declare const updateUserRewardManager: (poolManager: PoolRewardManager, userRewardManager: UserRewardManager, nowMs: number) => UserRewardManager;
|
|
117
|
+
export declare const findReserveOrThrow: (reserves: Reserve<string>[], coinType: string) => Reserve<string>;
|
|
118
|
+
export declare const computeBorrowWeight: (config: ReserveConfig) => BigNumber;
|
|
119
|
+
export declare const computeCloseLtv: (config: ReserveConfig) => BigNumber;
|
|
120
|
+
export type ObligationRefresh = {
|
|
121
|
+
deposits: Deposit[];
|
|
122
|
+
borrows: Borrow[];
|
|
123
|
+
userRewardManagers: UserRewardManager[];
|
|
124
|
+
depositedValueUsd: BigNumber;
|
|
125
|
+
allowedBorrowValueUsd: BigNumber;
|
|
126
|
+
unhealthyBorrowValueUsd: BigNumber;
|
|
127
|
+
unweightedBorrowedValueUsd: BigNumber;
|
|
128
|
+
weightedBorrowedValueUsd: BigNumber;
|
|
129
|
+
weightedBorrowedValueUpperBoundUsd: BigNumber;
|
|
130
|
+
};
|
|
131
|
+
/**
|
|
132
|
+
* Pure computation half of `refreshObligation`: same math, but returns fresh
|
|
133
|
+
* deposits/borrows/reward-manager arrays and the five USD aggregates instead
|
|
134
|
+
* of writing them onto `obligation`. Never touches `obligation` or any of its
|
|
135
|
+
* nested arrays — `refreshObligation`'s in-place `obligation.deposits[i] = ...`
|
|
136
|
+
* mutated the caller's original array too (only the top-level object was
|
|
137
|
+
* shallow-copied), silently corrupting `unrefreshedObligation` for anyone still
|
|
138
|
+
* holding a reference to it.
|
|
139
|
+
*/
|
|
140
|
+
export declare function computeObligationRefresh(obligation: Obligation<string>, refreshedReserves: Reserve<string>[], nowMs: number): ObligationRefresh;
|
|
116
141
|
export declare const refreshObligation: (unrefreshedObligation: Obligation<string>, refreshedReserves: Reserve<string>[]) => Obligation<string>;
|
|
117
142
|
export declare const numberToDecimal: (value: number) => Decimal;
|
|
118
143
|
export declare const stringToDecimal: (value: string) => Decimal;
|
|
@@ -121,4 +146,42 @@ export declare const getCTokenMarketValue: (reserve: Reserve<string>, depositedC
|
|
|
121
146
|
export declare const getCTokenMarketValueLowerBound: (reserve: Reserve<string>, depositedCTokenAmount: BigNumber) => BigNumber;
|
|
122
147
|
export declare const cTokenRatio: (reserve: Reserve<string>) => BigNumber;
|
|
123
148
|
export declare const totalSupply: (reserve: Reserve<string>) => BigNumber;
|
|
149
|
+
export declare const compoundBorrowedAmount: (snapshotAmount: BigNumber, currentCumulativeBorrowRate: BigNumber, snapshotCumulativeBorrowRate: BigNumber) => BigNumber;
|
|
124
150
|
export declare const compoundDebt: (borrow: Borrow, reserve: Reserve<string>) => Borrow;
|
|
151
|
+
export type ObligationHealthDepositInput = {
|
|
152
|
+
/** Matches `reserve.coinType.name` (unprefixed struct tag) — same field `refreshObligation` compares deposits against. */
|
|
153
|
+
coinType: string;
|
|
154
|
+
/** Raw ctoken unit count, i.e. `deposit.depositedCtokenAmount` as stored (Positions.quantity for a deposit row). */
|
|
155
|
+
depositedCtokenAmount: BigNumber;
|
|
156
|
+
};
|
|
157
|
+
export type ObligationHealthBorrowInput = {
|
|
158
|
+
coinType: string;
|
|
159
|
+
/** Human-scale underlying amount as of the stored snapshot (Positions.quantity for a borrow row — already ÷ WAD at write time, see dbClient.ts writeCachedObligations). */
|
|
160
|
+
borrowedAmountUnderlying: BigNumber;
|
|
161
|
+
/** Human-scale cumulative borrow rate factor as of the stored snapshot (Positions.cumulativeBorrowRate, a raw WAD integer, ÷ WAD). */
|
|
162
|
+
cumulativeBorrowRateAtSnapshot: BigNumber;
|
|
163
|
+
};
|
|
164
|
+
export type ObligationHealth = {
|
|
165
|
+
depositedValueUsd: BigNumber;
|
|
166
|
+
unhealthyBorrowValueUsd: BigNumber;
|
|
167
|
+
weightedBorrowedValueUsd: BigNumber;
|
|
168
|
+
};
|
|
169
|
+
/**
|
|
170
|
+
* The health-relevant subset of `refreshObligation` (deposit/borrow valuation
|
|
171
|
+
* and the two USD thresholds `shouldAttemptLiquidations` compares), computed
|
|
172
|
+
* from stored position state rather than a live on-chain `Obligation`.
|
|
173
|
+
*
|
|
174
|
+
* Deliberately does NOT touch reward-manager state (`refreshObligation`'s
|
|
175
|
+
* `updateUserRewardManager` calls) — that state isn't stored in `Positions`
|
|
176
|
+
* and doesn't participate in either threshold (LEN-576). Deliberately does
|
|
177
|
+
* NOT compute `allowedBorrowValueUsd`/`unweightedBorrowedValueUsd`/
|
|
178
|
+
* `weightedBorrowedValueUpperBoundUsd` — those exist for the risky-subset
|
|
179
|
+
* re-scan optimization in `obligationIsRisky`, which has no equivalent here
|
|
180
|
+
* (this function is cheap enough to run over every obligation every cycle).
|
|
181
|
+
*
|
|
182
|
+
* All math stays in BigNumber end-to-end — never construct a `Decimal` from
|
|
183
|
+
* a stored quantity; `numberToDecimal`/`stringToDecimal` round through a JS
|
|
184
|
+
* `number` (`Math.round`) and are lossy at WAD scale. `decimalToBigNumber` is
|
|
185
|
+
* only ever called on a genuine on-chain `Reserve` field below.
|
|
186
|
+
*/
|
|
187
|
+
export declare function computeObligationHealthFromPositions(deposits: ObligationHealthDepositInput[], borrows: ObligationHealthBorrowInput[], refreshedReserves: Reserve<string>[]): ObligationHealth;
|
package/utils/simulate.js
CHANGED
|
@@ -271,14 +271,17 @@ export const refreshReservePriceTolerant = async (reserves, pythConnection) => {
|
|
|
271
271
|
return { reserves: updatedReserves, unpricedCoinTypes };
|
|
272
272
|
};
|
|
273
273
|
export const updateUserRewardManager = (poolManager, userRewardManager, nowMs) => {
|
|
274
|
-
const updatedUserRewardManager = {
|
|
274
|
+
const updatedUserRewardManager = {
|
|
275
|
+
...userRewardManager,
|
|
276
|
+
rewards: [...userRewardManager.rewards],
|
|
277
|
+
};
|
|
275
278
|
for (let i = 0; i < poolManager.poolRewards.length; i++) {
|
|
276
279
|
const poolReward = poolManager.poolRewards[i];
|
|
277
280
|
if (poolReward == null) {
|
|
278
281
|
continue;
|
|
279
282
|
}
|
|
280
|
-
if (i >=
|
|
281
|
-
|
|
283
|
+
if (i >= updatedUserRewardManager.rewards.length) {
|
|
284
|
+
updatedUserRewardManager.rewards.push(null);
|
|
282
285
|
}
|
|
283
286
|
const oldReward = updatedUserRewardManager.rewards[i];
|
|
284
287
|
const reward = { ...oldReward };
|
|
@@ -305,66 +308,105 @@ export const updateUserRewardManager = (poolManager, userRewardManager, nowMs) =
|
|
|
305
308
|
updatedUserRewardManager.lastUpdateTimeMs = BigInt(nowMs);
|
|
306
309
|
return updatedUserRewardManager;
|
|
307
310
|
};
|
|
308
|
-
export const
|
|
309
|
-
const
|
|
310
|
-
|
|
311
|
+
export const findReserveOrThrow = (reserves, coinType) => {
|
|
312
|
+
const reserve = reserves.find((r) => r.coinType.name === coinType);
|
|
313
|
+
if (!reserve) {
|
|
314
|
+
throw new Error(`Unable to find reserve for coin type ${coinType}`);
|
|
315
|
+
}
|
|
316
|
+
return reserve;
|
|
317
|
+
};
|
|
318
|
+
// `borrowWeightBps` is a raw u64, so `bps / BigInt(10000)` is integer
|
|
319
|
+
// division and truncates a fractional weight (e.g. DEEP/WAL at 1.6x, IKA at
|
|
320
|
+
// 1.8x) down to 1.0x. On-chain `decimal::from_bps` scales first
|
|
321
|
+
// (`v * WAD / 10_000`); dividing in BigNumber here matches that.
|
|
322
|
+
export const computeBorrowWeight = (config) => new BigNumber(config.borrowWeightBps.toString()).div(10000);
|
|
323
|
+
export const computeCloseLtv = (config) => new BigNumber(config.closeLtvPct / 100);
|
|
324
|
+
/**
|
|
325
|
+
* Pure computation half of `refreshObligation`: same math, but returns fresh
|
|
326
|
+
* deposits/borrows/reward-manager arrays and the five USD aggregates instead
|
|
327
|
+
* of writing them onto `obligation`. Never touches `obligation` or any of its
|
|
328
|
+
* nested arrays — `refreshObligation`'s in-place `obligation.deposits[i] = ...`
|
|
329
|
+
* mutated the caller's original array too (only the top-level object was
|
|
330
|
+
* shallow-copied), silently corrupting `unrefreshedObligation` for anyone still
|
|
331
|
+
* holding a reference to it.
|
|
332
|
+
*/
|
|
333
|
+
export function computeObligationRefresh(obligation, refreshedReserves, nowMs) {
|
|
334
|
+
const userRewardManagers = [...obligation.userRewardManagers];
|
|
311
335
|
let depositValueUsd = new BigNumber(0);
|
|
312
336
|
let allowedBorrowValueUsd = new BigNumber(0);
|
|
313
337
|
let unhealthyBorrowValueUsd = new BigNumber(0);
|
|
314
|
-
|
|
315
|
-
const deposit = { ...
|
|
316
|
-
const reserve = refreshedReserves
|
|
317
|
-
if (!reserve) {
|
|
318
|
-
throw new Error(`Unable to find reserve for coin type ${deposit.coinType.name}`);
|
|
319
|
-
}
|
|
338
|
+
const deposits = obligation.deposits.map((unrefreshedDeposit) => {
|
|
339
|
+
const deposit = { ...unrefreshedDeposit };
|
|
340
|
+
const reserve = findReserveOrThrow(refreshedReserves, deposit.coinType.name);
|
|
320
341
|
const config = reserve.config.element;
|
|
321
|
-
|
|
322
|
-
updateUserRewardManager(reserve.depositsPoolRewardManager,
|
|
342
|
+
userRewardManagers[Number(deposit.userRewardManagerIndex)] =
|
|
343
|
+
updateUserRewardManager(reserve.depositsPoolRewardManager, userRewardManagers[Number(deposit.userRewardManagerIndex)], nowMs);
|
|
323
344
|
const marketValue = getCTokenMarketValue(reserve, new BigNumber(deposit.depositedCtokenAmount.toString()));
|
|
324
345
|
const marketValueLowerBound = getCTokenMarketValueLowerBound(reserve, new BigNumber(deposit.depositedCtokenAmount.toString()));
|
|
325
346
|
deposit.marketValue = stringToDecimal(marketValue.toString());
|
|
326
347
|
depositValueUsd = depositValueUsd.plus(new BigNumber(marketValue.toString()));
|
|
327
348
|
allowedBorrowValueUsd = allowedBorrowValueUsd.plus(marketValueLowerBound.multipliedBy(new BigNumber(config.openLtvPct / 100)));
|
|
328
|
-
unhealthyBorrowValueUsd = unhealthyBorrowValueUsd.plus(marketValue.multipliedBy(
|
|
329
|
-
|
|
330
|
-
}
|
|
331
|
-
obligation.unhealthyBorrowValueUsd = stringToDecimal(unhealthyBorrowValueUsd.toString());
|
|
332
|
-
obligation.allowedBorrowValueUsd = stringToDecimal(allowedBorrowValueUsd.toString());
|
|
333
|
-
obligation.depositedValueUsd = stringToDecimal(depositValueUsd.toString());
|
|
334
|
-
// Refresh borrows
|
|
349
|
+
unhealthyBorrowValueUsd = unhealthyBorrowValueUsd.plus(marketValue.multipliedBy(computeCloseLtv(config)));
|
|
350
|
+
return deposit;
|
|
351
|
+
});
|
|
335
352
|
let unweightedBorrowedValueUsd = new BigNumber(0);
|
|
336
353
|
let weightedBorrowedValueUsd = new BigNumber(0);
|
|
337
354
|
let weightedBorrowedValueUpperBoundUsd = new BigNumber(0);
|
|
338
|
-
|
|
339
|
-
const
|
|
340
|
-
const reserve = refreshedReserves.find((r) => r.coinType.name == unrefreshedBorrow.coinType.name);
|
|
341
|
-
if (!reserve) {
|
|
342
|
-
throw new Error(`Unable to find reserve for coin type ${unrefreshedBorrow.coinType.name}`);
|
|
343
|
-
}
|
|
355
|
+
const borrows = obligation.borrows.map((unrefreshedBorrow) => {
|
|
356
|
+
const reserve = findReserveOrThrow(refreshedReserves, unrefreshedBorrow.coinType.name);
|
|
344
357
|
const config = reserve.config.element;
|
|
345
358
|
const borrow = {
|
|
346
359
|
...compoundDebt(unrefreshedBorrow, reserve),
|
|
347
360
|
};
|
|
348
|
-
|
|
349
|
-
updateUserRewardManager(reserve.borrowsPoolRewardManager,
|
|
361
|
+
userRewardManagers[Number(borrow.userRewardManagerIndex)] =
|
|
362
|
+
updateUserRewardManager(reserve.borrowsPoolRewardManager, userRewardManagers[Number(borrow.userRewardManagerIndex)], nowMs);
|
|
350
363
|
const marketValue = decimalToBigNumber(borrow.borrowedAmount)
|
|
351
364
|
.multipliedBy(decimalToBigNumber(reserve.price))
|
|
352
365
|
.dividedBy(new BigNumber(10 ** reserve.mintDecimals));
|
|
353
366
|
const upperBoundPrice = BigNumber.max(decimalToBigNumber(reserve.price), decimalToBigNumber(reserve.smoothedPrice));
|
|
354
|
-
|
|
367
|
+
// `borrow.borrowedAmount`, not `reserve.borrowedAmount`: the reserve field
|
|
368
|
+
// is the whole reserve's debt, so using it scaled every borrower's upper
|
|
369
|
+
// bound by total reserve borrows. `obligation.move` passes
|
|
370
|
+
// `borrow.borrowed_amount` to both `market_value` and
|
|
371
|
+
// `market_value_upper_bound`, and this is what `is_healthy` tests — an
|
|
372
|
+
// obligation owing $100 of a reserve with $22M borrowed read as $22M.
|
|
373
|
+
const marketValueUpperBound = decimalToBigNumber(borrow.borrowedAmount)
|
|
355
374
|
.multipliedBy(upperBoundPrice)
|
|
356
375
|
.dividedBy(new BigNumber(10 ** reserve.mintDecimals));
|
|
357
376
|
borrow.marketValue = stringToDecimal(marketValue.toString());
|
|
358
377
|
unweightedBorrowedValueUsd = unweightedBorrowedValueUsd.plus(marketValue);
|
|
359
|
-
const borrowWeight =
|
|
378
|
+
const borrowWeight = computeBorrowWeight(config);
|
|
360
379
|
weightedBorrowedValueUsd = weightedBorrowedValueUsd.plus(marketValue.multipliedBy(borrowWeight));
|
|
361
380
|
weightedBorrowedValueUpperBoundUsd =
|
|
362
381
|
weightedBorrowedValueUpperBoundUsd.plus(marketValueUpperBound.multipliedBy(borrowWeight));
|
|
363
|
-
|
|
364
|
-
|
|
365
|
-
|
|
366
|
-
|
|
367
|
-
|
|
382
|
+
return borrow;
|
|
383
|
+
});
|
|
384
|
+
return {
|
|
385
|
+
deposits,
|
|
386
|
+
borrows,
|
|
387
|
+
userRewardManagers: userRewardManagers,
|
|
388
|
+
depositedValueUsd: depositValueUsd,
|
|
389
|
+
allowedBorrowValueUsd,
|
|
390
|
+
unhealthyBorrowValueUsd,
|
|
391
|
+
unweightedBorrowedValueUsd,
|
|
392
|
+
weightedBorrowedValueUsd,
|
|
393
|
+
weightedBorrowedValueUpperBoundUsd,
|
|
394
|
+
};
|
|
395
|
+
}
|
|
396
|
+
export const refreshObligation = (unrefreshedObligation, refreshedReserves) => {
|
|
397
|
+
const refresh = computeObligationRefresh(unrefreshedObligation, refreshedReserves, Date.now());
|
|
398
|
+
return {
|
|
399
|
+
...unrefreshedObligation,
|
|
400
|
+
deposits: refresh.deposits,
|
|
401
|
+
borrows: refresh.borrows,
|
|
402
|
+
userRewardManagers: refresh.userRewardManagers,
|
|
403
|
+
depositedValueUsd: stringToDecimal(refresh.depositedValueUsd.toString()),
|
|
404
|
+
allowedBorrowValueUsd: stringToDecimal(refresh.allowedBorrowValueUsd.toString()),
|
|
405
|
+
unhealthyBorrowValueUsd: stringToDecimal(refresh.unhealthyBorrowValueUsd.toString()),
|
|
406
|
+
unweightedBorrowedValueUsd: stringToDecimal(refresh.unweightedBorrowedValueUsd.toString()),
|
|
407
|
+
weightedBorrowedValueUsd: stringToDecimal(refresh.weightedBorrowedValueUsd.toString()),
|
|
408
|
+
weightedBorrowedValueUpperBoundUsd: stringToDecimal(refresh.weightedBorrowedValueUpperBoundUsd.toString()),
|
|
409
|
+
};
|
|
368
410
|
};
|
|
369
411
|
export const numberToDecimal = (value) => {
|
|
370
412
|
const adjustedValue = Math.round(value * +WAD);
|
|
@@ -400,13 +442,62 @@ export const totalSupply = (reserve) => {
|
|
|
400
442
|
.plus(decimalToBigNumber(reserve.borrowedAmount))
|
|
401
443
|
.minus(decimalToBigNumber(reserve.unclaimedSpreadFees));
|
|
402
444
|
};
|
|
445
|
+
// A snapshot's cumulative borrow rate is only ever a divisor here because
|
|
446
|
+
// on-chain it's initialized to 1 and monotonically increases — never 0. That
|
|
447
|
+
// invariant is enforced on-chain, not by this function's callers (one of
|
|
448
|
+
// them reads a Postgres snapshot, not a live `Reserve`), so it's checked
|
|
449
|
+
// here rather than assumed.
|
|
450
|
+
export const compoundBorrowedAmount = (snapshotAmount, currentCumulativeBorrowRate, snapshotCumulativeBorrowRate) => {
|
|
451
|
+
if (snapshotCumulativeBorrowRate.isZero()) {
|
|
452
|
+
throw new Error("Cannot compound a borrow whose snapshot cumulativeBorrowRate is 0");
|
|
453
|
+
}
|
|
454
|
+
return snapshotAmount.multipliedBy(currentCumulativeBorrowRate.dividedBy(snapshotCumulativeBorrowRate));
|
|
455
|
+
};
|
|
403
456
|
export const compoundDebt = (borrow, reserve) => {
|
|
404
457
|
const borrowCopy = { ...borrow };
|
|
405
458
|
const newCumulativeBorrowRate = decimalToBigNumber(reserve.cumulativeBorrowRate);
|
|
406
|
-
|
|
407
|
-
borrowCopy.borrowedAmount = stringToDecimal(decimalToBigNumber(borrow.borrowedAmount)
|
|
408
|
-
.multipliedBy(compoundedInterestRate)
|
|
409
|
-
.toString());
|
|
459
|
+
borrowCopy.borrowedAmount = stringToDecimal(compoundBorrowedAmount(decimalToBigNumber(borrow.borrowedAmount), newCumulativeBorrowRate, decimalToBigNumber(borrow.cumulativeBorrowRate)).toString());
|
|
410
460
|
borrowCopy.cumulativeBorrowRate = stringToDecimal(newCumulativeBorrowRate.toString());
|
|
411
461
|
return borrowCopy;
|
|
412
462
|
};
|
|
463
|
+
/**
|
|
464
|
+
* The health-relevant subset of `refreshObligation` (deposit/borrow valuation
|
|
465
|
+
* and the two USD thresholds `shouldAttemptLiquidations` compares), computed
|
|
466
|
+
* from stored position state rather than a live on-chain `Obligation`.
|
|
467
|
+
*
|
|
468
|
+
* Deliberately does NOT touch reward-manager state (`refreshObligation`'s
|
|
469
|
+
* `updateUserRewardManager` calls) — that state isn't stored in `Positions`
|
|
470
|
+
* and doesn't participate in either threshold (LEN-576). Deliberately does
|
|
471
|
+
* NOT compute `allowedBorrowValueUsd`/`unweightedBorrowedValueUsd`/
|
|
472
|
+
* `weightedBorrowedValueUpperBoundUsd` — those exist for the risky-subset
|
|
473
|
+
* re-scan optimization in `obligationIsRisky`, which has no equivalent here
|
|
474
|
+
* (this function is cheap enough to run over every obligation every cycle).
|
|
475
|
+
*
|
|
476
|
+
* All math stays in BigNumber end-to-end — never construct a `Decimal` from
|
|
477
|
+
* a stored quantity; `numberToDecimal`/`stringToDecimal` round through a JS
|
|
478
|
+
* `number` (`Math.round`) and are lossy at WAD scale. `decimalToBigNumber` is
|
|
479
|
+
* only ever called on a genuine on-chain `Reserve` field below.
|
|
480
|
+
*/
|
|
481
|
+
export function computeObligationHealthFromPositions(deposits, borrows, refreshedReserves) {
|
|
482
|
+
let depositValueUsd = new BigNumber(0);
|
|
483
|
+
let unhealthyBorrowValueUsd = new BigNumber(0);
|
|
484
|
+
for (const deposit of deposits) {
|
|
485
|
+
const reserve = findReserveOrThrow(refreshedReserves, deposit.coinType);
|
|
486
|
+
const config = reserve.config.element;
|
|
487
|
+
const marketValue = getCTokenMarketValue(reserve, deposit.depositedCtokenAmount);
|
|
488
|
+
depositValueUsd = depositValueUsd.plus(marketValue);
|
|
489
|
+
unhealthyBorrowValueUsd = unhealthyBorrowValueUsd.plus(marketValue.multipliedBy(computeCloseLtv(config)));
|
|
490
|
+
}
|
|
491
|
+
let weightedBorrowedValueUsd = new BigNumber(0);
|
|
492
|
+
for (const borrow of borrows) {
|
|
493
|
+
const reserve = findReserveOrThrow(refreshedReserves, borrow.coinType);
|
|
494
|
+
const config = reserve.config.element;
|
|
495
|
+
const currentBorrowedAmountUnderlying = compoundBorrowedAmount(borrow.borrowedAmountUnderlying, decimalToBigNumber(reserve.cumulativeBorrowRate), borrow.cumulativeBorrowRateAtSnapshot);
|
|
496
|
+
const marketValue = currentBorrowedAmountUnderlying
|
|
497
|
+
.multipliedBy(decimalToBigNumber(reserve.price))
|
|
498
|
+
.dividedBy(new BigNumber(10 ** reserve.mintDecimals));
|
|
499
|
+
const borrowWeight = computeBorrowWeight(config);
|
|
500
|
+
weightedBorrowedValueUsd = weightedBorrowedValueUsd.plus(marketValue.multipliedBy(borrowWeight));
|
|
501
|
+
}
|
|
502
|
+
return { depositedValueUsd: depositValueUsd, unhealthyBorrowValueUsd, weightedBorrowedValueUsd };
|
|
503
|
+
}
|
package/lib/pythAdapter.d.ts
DELETED
|
@@ -1,74 +0,0 @@
|
|
|
1
|
-
import { SuiGrpcClient } from "@mysten/sui/grpc";
|
|
2
|
-
/**
|
|
3
|
-
* A Move object as the legacy JSON-RPC shape reported it.
|
|
4
|
-
*
|
|
5
|
-
* `fields` stays `unknown` on purpose: it is a decoded Move struct whose shape
|
|
6
|
-
* depends entirely on the object being read, and the callers that consume this
|
|
7
|
-
* adapter narrow it themselves. That is a genuine unknown, not the blanket `any`
|
|
8
|
-
* this module used to return.
|
|
9
|
-
*/
|
|
10
|
-
export interface LegacyObjectResponse {
|
|
11
|
-
data: {
|
|
12
|
-
objectId: string;
|
|
13
|
-
type: string;
|
|
14
|
-
content: {
|
|
15
|
-
dataType: "moveObject";
|
|
16
|
-
fields: unknown;
|
|
17
|
-
type: string;
|
|
18
|
-
};
|
|
19
|
-
} | null;
|
|
20
|
-
}
|
|
21
|
-
/** A coin as the legacy JSON-RPC `getCoins` reported it. */
|
|
22
|
-
export interface LegacyCoin {
|
|
23
|
-
coinObjectId: string;
|
|
24
|
-
version: string | bigint | number;
|
|
25
|
-
digest: string;
|
|
26
|
-
balance: string | bigint | number;
|
|
27
|
-
coinType: string;
|
|
28
|
-
}
|
|
29
|
-
/**
|
|
30
|
-
* The legacy JSON-RPC surface this adapter provides.
|
|
31
|
-
*
|
|
32
|
-
* Explicit rather than inferred, and that is the whole point: this type has NO
|
|
33
|
-
* `core` property, so handing the adapter to something expecting `@mysten/sui`'s
|
|
34
|
-
* `ClientWithCoreApi` — which is what pyth v4's `SuiPythClient` wants — is now a
|
|
35
|
-
* COMPILE error.
|
|
36
|
-
*
|
|
37
|
-
* It used to be inferred as `any` (measurably: `IsAny<ReturnType<...>>` resolved
|
|
38
|
-
* to true), which made exactly that mismatch invisible to tsc. It shipped twice:
|
|
39
|
-
* @suilend/sdk 6.0.0/6.0.1 broke every deposit/withdraw/borrow/repay, and
|
|
40
|
-
* steamm-sdk 3.5.0 broke the admin oracle flow, both with
|
|
41
|
-
* "Cannot read properties of undefined (reading 'getDynamicField')". Neither was
|
|
42
|
-
* caught by a typecheck; both were found in production.
|
|
43
|
-
*
|
|
44
|
-
* Keep this typed. A third instance should fail to compile, not to run.
|
|
45
|
-
*/
|
|
46
|
-
export interface LegacyJsonRpcAdapter {
|
|
47
|
-
getObject(args: {
|
|
48
|
-
id: string;
|
|
49
|
-
options?: unknown;
|
|
50
|
-
}): Promise<LegacyObjectResponse>;
|
|
51
|
-
getCoins(args: {
|
|
52
|
-
owner: string;
|
|
53
|
-
coinType?: string;
|
|
54
|
-
cursor?: string | null;
|
|
55
|
-
}): Promise<{
|
|
56
|
-
data: LegacyCoin[];
|
|
57
|
-
nextCursor: string | null | undefined;
|
|
58
|
-
hasNextPage: boolean;
|
|
59
|
-
}>;
|
|
60
|
-
getDynamicFieldObject(args: {
|
|
61
|
-
parentId: string;
|
|
62
|
-
name: {
|
|
63
|
-
type: string;
|
|
64
|
-
value: unknown;
|
|
65
|
-
};
|
|
66
|
-
}): Promise<LegacyObjectResponse>;
|
|
67
|
-
}
|
|
68
|
-
/**
|
|
69
|
-
* Adapter that wraps SuiGrpcClient to provide the legacy SuiClient (JSON-RPC)
|
|
70
|
-
* interface expected by third-party SDKs like @pythnetwork/pyth-sui-js.
|
|
71
|
-
*
|
|
72
|
-
* Maps old JSON-RPC method signatures to the new gRPC equivalents.
|
|
73
|
-
*/
|
|
74
|
-
export declare function createJsonRpcAdapter(suiGrpcClient: SuiGrpcClient): LegacyJsonRpcAdapter;
|
package/lib/pythAdapter.js
DELETED
|
@@ -1,182 +0,0 @@
|
|
|
1
|
-
import { bcs } from "@mysten/sui/bcs";
|
|
2
|
-
const PYTH_STATE_ID = "0x1f9310238ee9298fb703c3419030b35b22bb1cc37113e3bb5007c99aec79e5b8";
|
|
3
|
-
const PYTH_PRICE_TABLE_ID = "0x234c9ffca44613ab87a2711325b6e17bad9ece0449b917c8bd9c0ad7a0506cc2";
|
|
4
|
-
const PYTH_PACKAGE = "0x8d97f1cd6ac663735be08d1d2b6d02a159e711586461306ce60a2b7a6a565a9e";
|
|
5
|
-
/**
|
|
6
|
-
* Adapter that wraps SuiGrpcClient to provide the legacy SuiClient (JSON-RPC)
|
|
7
|
-
* interface expected by third-party SDKs like @pythnetwork/pyth-sui-js.
|
|
8
|
-
*
|
|
9
|
-
* Maps old JSON-RPC method signatures to the new gRPC equivalents.
|
|
10
|
-
*/
|
|
11
|
-
export function createJsonRpcAdapter(suiGrpcClient) {
|
|
12
|
-
return {
|
|
13
|
-
async getObject({ id }) {
|
|
14
|
-
const { object } = await suiGrpcClient.getObject({
|
|
15
|
-
objectId: id,
|
|
16
|
-
include: { json: true },
|
|
17
|
-
});
|
|
18
|
-
return {
|
|
19
|
-
data: {
|
|
20
|
-
objectId: object.objectId,
|
|
21
|
-
type: object.type,
|
|
22
|
-
content: {
|
|
23
|
-
dataType: "moveObject",
|
|
24
|
-
fields: wrapNestedObjects(object.json),
|
|
25
|
-
type: object.type,
|
|
26
|
-
},
|
|
27
|
-
},
|
|
28
|
-
};
|
|
29
|
-
},
|
|
30
|
-
async getCoins({ owner, coinType, cursor, }) {
|
|
31
|
-
const result = await suiGrpcClient.listCoins({
|
|
32
|
-
owner,
|
|
33
|
-
coinType,
|
|
34
|
-
cursor,
|
|
35
|
-
});
|
|
36
|
-
return {
|
|
37
|
-
data: result.objects.map((coin) => ({
|
|
38
|
-
coinObjectId: coin.objectId,
|
|
39
|
-
version: coin.version,
|
|
40
|
-
digest: coin.digest,
|
|
41
|
-
balance: coin.balance,
|
|
42
|
-
coinType: coin.type,
|
|
43
|
-
})),
|
|
44
|
-
nextCursor: result.cursor,
|
|
45
|
-
hasNextPage: result.hasNextPage,
|
|
46
|
-
};
|
|
47
|
-
},
|
|
48
|
-
async getDynamicFieldObject({ parentId, name, }) {
|
|
49
|
-
// The gRPC node may not have dynamic field wrapper objects indexed.
|
|
50
|
-
// For known lookups (Pyth price table), use hardcoded values.
|
|
51
|
-
if (parentId === PYTH_STATE_ID &&
|
|
52
|
-
name.type === "vector<u8>" &&
|
|
53
|
-
name.value === "price_info") {
|
|
54
|
-
return {
|
|
55
|
-
data: {
|
|
56
|
-
objectId: PYTH_PRICE_TABLE_ID,
|
|
57
|
-
type: `0x2::table::Table<${PYTH_PACKAGE}::price_identifier::PriceIdentifier, 0x2::object::ID>`,
|
|
58
|
-
content: {
|
|
59
|
-
dataType: "moveObject",
|
|
60
|
-
fields: null,
|
|
61
|
-
type: `0x2::table::Table<${PYTH_PACKAGE}::price_identifier::PriceIdentifier, 0x2::object::ID>`,
|
|
62
|
-
},
|
|
63
|
-
},
|
|
64
|
-
};
|
|
65
|
-
}
|
|
66
|
-
const { dynamicField } = await suiGrpcClient.getDynamicField({
|
|
67
|
-
parentId,
|
|
68
|
-
name: {
|
|
69
|
-
type: name.type,
|
|
70
|
-
bcs: dynamicField_encodeName(name),
|
|
71
|
-
},
|
|
72
|
-
});
|
|
73
|
-
if (dynamicField.$kind === "DynamicObject" && dynamicField.childId) {
|
|
74
|
-
try {
|
|
75
|
-
const childObject = await suiGrpcClient
|
|
76
|
-
.getObject({
|
|
77
|
-
objectId: dynamicField.childId,
|
|
78
|
-
include: { json: true },
|
|
79
|
-
})
|
|
80
|
-
.then((r) => r.object);
|
|
81
|
-
return {
|
|
82
|
-
data: {
|
|
83
|
-
objectId: childObject.objectId,
|
|
84
|
-
type: childObject.type,
|
|
85
|
-
content: {
|
|
86
|
-
dataType: "moveObject",
|
|
87
|
-
fields: childObject.json,
|
|
88
|
-
type: childObject.type,
|
|
89
|
-
},
|
|
90
|
-
},
|
|
91
|
-
};
|
|
92
|
-
}
|
|
93
|
-
catch {
|
|
94
|
-
return {
|
|
95
|
-
data: {
|
|
96
|
-
objectId: dynamicField.childId,
|
|
97
|
-
type: dynamicField.valueType,
|
|
98
|
-
content: {
|
|
99
|
-
dataType: "moveObject",
|
|
100
|
-
fields: null,
|
|
101
|
-
type: dynamicField.valueType,
|
|
102
|
-
},
|
|
103
|
-
},
|
|
104
|
-
};
|
|
105
|
-
}
|
|
106
|
-
}
|
|
107
|
-
const valueBcs = dynamicField.value?.bcs;
|
|
108
|
-
const type = dynamicField.valueType ?? dynamicField.type;
|
|
109
|
-
let fields = null;
|
|
110
|
-
if (valueBcs && valueBcs.length > 0) {
|
|
111
|
-
if (type === "0x2::object::ID" ||
|
|
112
|
-
type.endsWith("::object::ID") ||
|
|
113
|
-
valueBcs.length === 32) {
|
|
114
|
-
const id = "0x" +
|
|
115
|
-
Array.from(valueBcs)
|
|
116
|
-
.map((b) => b.toString(16).padStart(2, "0"))
|
|
117
|
-
.join("");
|
|
118
|
-
fields = { value: id };
|
|
119
|
-
}
|
|
120
|
-
}
|
|
121
|
-
return {
|
|
122
|
-
data: {
|
|
123
|
-
objectId: dynamicField.fieldId,
|
|
124
|
-
type,
|
|
125
|
-
content: {
|
|
126
|
-
dataType: "moveObject",
|
|
127
|
-
fields,
|
|
128
|
-
type,
|
|
129
|
-
},
|
|
130
|
-
},
|
|
131
|
-
};
|
|
132
|
-
},
|
|
133
|
-
};
|
|
134
|
-
}
|
|
135
|
-
function dynamicField_encodeName(name) {
|
|
136
|
-
// The pyth client passes structured names like:
|
|
137
|
-
// { type: "vector<u8>", value: "price_info" }
|
|
138
|
-
// { type: "..::PriceIdentifier", value: { bytes: number[] } }
|
|
139
|
-
// The gRPC getDynamicField expects BCS-encoded name bytes.
|
|
140
|
-
if (name.type === "vector<u8>") {
|
|
141
|
-
const bytes = typeof name.value === "string"
|
|
142
|
-
? new TextEncoder().encode(name.value)
|
|
143
|
-
: new Uint8Array(name.value);
|
|
144
|
-
return bcs.vector(bcs.u8()).serialize(Array.from(bytes)).toBytes();
|
|
145
|
-
}
|
|
146
|
-
if (name.value &&
|
|
147
|
-
typeof name.value === "object" &&
|
|
148
|
-
"bytes" in name.value &&
|
|
149
|
-
Array.isArray(name.value.bytes)) {
|
|
150
|
-
const struct = bcs.struct("PriceIdentifier", {
|
|
151
|
-
bytes: bcs.vector(bcs.u8()),
|
|
152
|
-
});
|
|
153
|
-
return struct.serialize(name.value).toBytes();
|
|
154
|
-
}
|
|
155
|
-
if (typeof name.value === "boolean") {
|
|
156
|
-
return bcs.bool().serialize(name.value).toBytes();
|
|
157
|
-
}
|
|
158
|
-
throw new Error(`jsonRpcAdapter: unsupported dynamic field name type "${name.type}"`);
|
|
159
|
-
}
|
|
160
|
-
/**
|
|
161
|
-
* gRPC Value.toJson() returns flat objects, but the JSON-RPC format wraps
|
|
162
|
-
* nested struct fields in { fields: { ... } }. This recursively wraps any
|
|
163
|
-
* nested plain object values so legacy SDKs (e.g. Pyth) can traverse them.
|
|
164
|
-
*/
|
|
165
|
-
function wrapNestedObjects(obj) {
|
|
166
|
-
if (obj === null || obj === undefined)
|
|
167
|
-
return obj;
|
|
168
|
-
if (Array.isArray(obj))
|
|
169
|
-
return obj.map(wrapNestedObjects);
|
|
170
|
-
if (typeof obj !== "object")
|
|
171
|
-
return obj;
|
|
172
|
-
const result = {};
|
|
173
|
-
for (const [key, value] of Object.entries(obj)) {
|
|
174
|
-
if (value !== null && typeof value === "object" && !Array.isArray(value)) {
|
|
175
|
-
result[key] = { fields: wrapNestedObjects(value) };
|
|
176
|
-
}
|
|
177
|
-
else {
|
|
178
|
-
result[key] = value;
|
|
179
|
-
}
|
|
180
|
-
}
|
|
181
|
-
return result;
|
|
182
|
-
}
|