@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 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, not through createJsonRpcAdapter.
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 adapter, which produces
144
- // exactly that legacy shape and has no `.core`, made every call through
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":"6.0.3","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/pythAdapter":"./lib/pythAdapter.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.3.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":"^9.1.2","bn.js":"^5.2.2","crypto-js":"^4.2.0","lodash":"^4.17.21","p-limit":"3.1.0","uuid":"^11.0.3"},"devDependencies":{"@mysten/bcs":"^2.0.5","@mysten/sui":"2.17.0","@suilend/sui-core":"^1.0.0","@tsconfig/recommended":"^1.0.8","@types/bn.js":"^5.2.0","@types/lodash":"^4.17.20","@types/node":"^22.9.0","prettier":"^3.3.3","ts-node":"^10.9.2","typescript":"^6.0.3","vitest":"4.1.0"},"peerDependencies":{"@mysten/bcs":"^2.0.5","@mysten/sui":"2.17.0","@suilend/sui-core":"^1.0.0"},"type":"module"}
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"}
@@ -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>;
@@ -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 = { ...userRewardManager };
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 >= userRewardManager.rewards.length) {
281
- userRewardManager.rewards.push(null);
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 refreshObligation = (unrefreshedObligation, refreshedReserves) => {
309
- const obligation = { ...unrefreshedObligation };
310
- // Refresh Deposits
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
- for (let i = 0; i < obligation.deposits.length; i++) {
315
- const deposit = { ...obligation.deposits[i] };
316
- const reserve = refreshedReserves.find((r) => r.coinType.name === deposit.coinType.name);
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
- obligation.userRewardManagers[Number(deposit.userRewardManagerIndex)] =
322
- updateUserRewardManager(reserve.depositsPoolRewardManager, obligation.userRewardManagers[Number(deposit.userRewardManagerIndex)], Date.now());
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(new BigNumber(config.closeLtvPct / 100)));
329
- obligation.deposits[i] = deposit;
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
- for (let i = 0; i < obligation.borrows.length; i++) {
339
- const unrefreshedBorrow = obligation.borrows[i];
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
- obligation.userRewardManagers[Number(borrow.userRewardManagerIndex)] =
349
- updateUserRewardManager(reserve.borrowsPoolRewardManager, obligation.userRewardManagers[Number(borrow.userRewardManagerIndex)], Date.now());
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
- const marketValueUpperBound = decimalToBigNumber(reserve.borrowedAmount)
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 = new BigNumber((config.borrowWeightBps / BigInt(10000)).toString());
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
- obligation.unweightedBorrowedValueUsd = stringToDecimal(unweightedBorrowedValueUsd.toString());
365
- obligation.weightedBorrowedValueUpperBoundUsd = stringToDecimal(weightedBorrowedValueUpperBoundUsd.toString());
366
- obligation.weightedBorrowedValueUsd = stringToDecimal(weightedBorrowedValueUsd.toString());
367
- return obligation;
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
- const compoundedInterestRate = newCumulativeBorrowRate.dividedBy(decimalToBigNumber(borrow.cumulativeBorrowRate));
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
+ }
@@ -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;
@@ -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
- }