@instadapp/fluid-sui-read-sdk 0.1.0 → 0.3.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (4) hide show
  1. package/README.md +148 -21
  2. package/dist/index.d.mts +1229 -995
  3. package/dist/index.mjs +719 -311
  4. package/package.json +1 -1
package/dist/index.mjs CHANGED
@@ -1,9 +1,9 @@
1
1
  import { t as __exportAll } from "./chunk-15K8U1wQ.mjs";
2
- import { deriveDynamicFieldID, deriveObjectID, normalizeStructTag, normalizeSuiAddress, normalizeSuiObjectId, parseStructTag, toHex } from "@mysten/sui/utils";
2
+ import { deriveDynamicFieldID, deriveObjectID, fromBase64, normalizeStructTag, normalizeSuiAddress, normalizeSuiObjectId, parseStructTag, toHex } from "@mysten/sui/utils";
3
+ import { ObjectError } from "@mysten/sui/client";
3
4
  import { SuiGrpcClient } from "@mysten/sui/grpc";
4
5
  import { SuiJsonRpcClient } from "@mysten/sui/jsonRpc";
5
6
  import { bcs } from "@mysten/sui/bcs";
6
- import { ObjectError } from "@mysten/sui/client";
7
7
  import { Transaction } from "@mysten/sui/transactions";
8
8
 
9
9
  //#region src/config.ts
@@ -11,6 +11,8 @@ const same = (id) => ({
11
11
  packageId: id,
12
12
  originalId: id
13
13
  });
14
+ /** Liquidity packages known to run the pre-audit-fix maths (deployed bytecode checked 2026-10-26). */
15
+ const PRE_AUDIT_LIQUIDITY = new Set([normalizeSuiObjectId("0xd0f7e9baf5bb8c13444fc7abc562103b7632acea5f290ed7ab6bbe6f2191febb")]);
14
16
  const TOKENS = [
15
17
  "0x2::sui::SUI",
16
18
  "0xdba34672e30cb065b1f93e3ab55318768fd6fef66c15942c9f7cb846e2f900e7::usdc::USDC",
@@ -41,6 +43,7 @@ const STAGING = {
41
43
  oracle: same("0xc1e7632c9847370a0f00298defa15d42fcb0e3191cad3bee8451cab5ce144814"),
42
44
  vaults: same("0x08a3f6777d66c82ded41f5c02f902be34407005b2e53649b4e4da0bb63ef6f20")
43
45
  },
46
+ mathVariant: "pre-audit",
44
47
  objects: {
45
48
  liquidityRegistry: "0x2b712ae7c38f4b4a09498565a4df8184bda875506b9f65ba8f64cf5662731ccc",
46
49
  lendingRegistry: "0xc4f71cb64e6c01216f18fbea012176aa2f3a0a747347c914627cbb5aa0e569d2",
@@ -52,8 +55,7 @@ const STAGING = {
52
55
  oracleAuth: "0x6133bd13291e1d04526c69957b27cf0a9d7422059873ad00e3e34ff1b1ba07d9",
53
56
  vaultAdmin: "0x72ef539f4827cd055e38a3c2385a313831652fe4bde650a5d099dfdb95fc98d3"
54
57
  },
55
- oracleVariant: "v2",
56
- oracleV2: {
58
+ oracle: {
57
59
  redstoneAdapter: "0x22794c3a37c5320e5acb6b9cdba6e256bc08867e9de8afd2a4b5d8ea7061fea3",
58
60
  redstonePackage: "0xfa84e5620fda505bbeb9178316a555e1db36f8932dee44210dd88a6464a76ef6",
59
61
  pythPriceInfoObjects: { "0x23d7315113f5b1d3ba7a83604c44b94d79f4fd69af77f804fc7f920a6dc65744": "0x89b2add829cb6fcd017153fff428bc9faec4d06d643ecfc435af5b55a9e987f0" },
@@ -64,48 +66,55 @@ const STAGING = {
64
66
  rpcUrls: ["https://fullnode.mainnet.sui.io:443"],
65
67
  graphqlUrl: "https://graphql.mainnet.sui.io/graphql"
66
68
  };
69
+ const USDC = "0xdba34672e30cb065b1f93e3ab55318768fd6fef66c15942c9f7cb846e2f900e7::usdc::USDC";
70
+ const HBTC = "0xd30fb3f8914efdbeb7b8e85fae1d65dc584b44a1281f66737b92bb9f03a59d06::btc::BTC";
67
71
  /**
68
- * SUPERSEDED. 2026-09-18 deployment: interim admin oracle (`view::quote`, vault 1 on
69
- * 0xf36d0d95…651f) + the vaults package built on it. Objects still exist on chain; kept only to
70
- * read historical state.
72
+ * PRODUCTION deployment on Sui mainnet (2026-11-02), same ids as fluid-packages-sui
73
+ * deployments/mainnet.json + mainnet-markets.json and the write SDK. Day one: fUSDC and
74
+ * vault 1 = VaultMarket<hBTC, USDC> 0x1aa7…a624 on oracle nonce 1 0x6f74…3784 (RedStone BTC
75
+ * main + Pyth BTC/USD guard). Audited maths. Network "mainnet".
71
76
  */
72
- const STAGING_LEGACY = {
77
+ const PROD = {
73
78
  network: "mainnet",
74
79
  packages: {
75
- ...COMMON_IDS,
76
- oracle: same("0xebe2409c7ea9636f0e446797515cbee2ab809d898b06d99179d9eda59e1e42d5"),
77
- vaults: same("0xf0f5a9c105384d5b79e212a9c9d3adfa5d030ef4c0cf9e22fa48674f9ad3652a")
80
+ library: same("0xdb1caa95193a0bda87e56a9aaf9878672ca8bea7039447e5c789a572a0621969"),
81
+ liquidity: same("0x03dbce0f4cbd6325ca81414cc63a82f35e32bb941610b9120fab0997bc70daa3"),
82
+ lending: same("0xc63078660d7e3ed001e565b8bd7e2e6eb7313ef809051165cda901621e1db842"),
83
+ lendingRewards: same("0x8269897ee68730b4bde175c883719f1fa108509113c2c177ad1b456bb3782ba2"),
84
+ oracle: same("0x2caaa38d2c1c424d8ec79750c110d2211f2e06d66a9a261204f721175da15bcc"),
85
+ vaults: same("0x937156ee38d5c0a9e061dedd549867aeee88c478fad7f3213b66ca4f95906743")
78
86
  },
79
87
  objects: {
80
- liquidityRegistry: "0x2b712ae7c38f4b4a09498565a4df8184bda875506b9f65ba8f64cf5662731ccc",
81
- lendingRegistry: "0xc4f71cb64e6c01216f18fbea012176aa2f3a0a747347c914627cbb5aa0e569d2",
82
- oracleRegistry: "0xe645573b6923398b4fbe952fe7e4aa4acc312b0a1c74c7d7417f812613f3173a",
83
- vaultRegistry: "0xef8792ea0c069b7bd8e5c86291180135ff391ce5d5bdfaf205aeee593a486f24",
84
- liquidityAuth: "0x85b19be86131a7a4b378c1b78e8e9b765a30c8c770de0645bfe0e1cb4da5ddb5",
85
- lendingAuth: "0xc145f5f8aae7d72bf56a2ba82708976c057367160857146beedfa0b5409a5702",
86
- vaultAdmin: "0x3ab15bc5037f34fcc5b763e4af4c83f2166811444859616d3ffe5049ef808dd1"
88
+ liquidityRegistry: "0xddeb96aeff09698e7fdcdefb6923642d0e2da1febcaf656371cf7e55bb339a0a",
89
+ lendingRegistry: "0x98307ab7e769b60257f4b12c20e0976fb57bbe898409b568a3044cf94b3f5647",
90
+ oracleRegistry: "0x359f818813a10a4bc3862536dafa6bc2bef0bf3597b090539fd05a89cf8fb368",
91
+ vaultRegistry: "0xe598cc749e0907d6454fdf779611b2efee7fdb2d756ff2706385bfed493bc608",
92
+ liquidityAuth: "0x1fc3d11285cd7b7ed44bf92f5df3a856c1a45699f59168fd878dfa4eae8e459f",
93
+ lendingAuth: "0x6e8ca10c5e6f49984106e38a3b6ef49c05a5a5a01f77b04c754e4d6c1e0ec8f7",
94
+ rewardsAuth: "0xaaf5efdfa300286a719ca8131bcc5f70bc1d3b1fb18a2886f4da04072b5b3bd1",
95
+ oracleAuth: "0xb498982435b0fdc18126d5febf1327af5dcd02b2e3377b6e5c37ab3eddf08ca8",
96
+ vaultAdmin: "0x2b3d9ccfb6e1da6ff8acfcb9c56c898a06ae7f3d6f37e3e64e7df34a60ccd887"
87
97
  },
88
- oracleVariant: "admin",
89
- tokens: TOKENS,
98
+ oracle: {
99
+ redstoneAdapter: STAGING.oracle.redstoneAdapter,
100
+ redstonePackage: STAGING.oracle.redstonePackage,
101
+ pythPriceInfoObjects: { "0xe62df6c8b4a85fe1a67db44dc12de5db330f7ac66b72dc658afedf0f4a415b43": "0xe64832d1b4c9a75139a7313aee69da891297d8397fda2863b5bdcc4af0b79758" },
102
+ pythStateId: STAGING.oracle.pythStateId,
103
+ wormholeStateId: STAGING.oracle.wormholeStateId
104
+ },
105
+ tokens: [USDC, HBTC],
90
106
  rpcUrls: ["https://fullnode.mainnet.sui.io:443"],
91
107
  graphqlUrl: "https://graphql.mainnet.sui.io/graphql"
92
108
  };
93
- /**
94
- * @deprecated Misleading name kept for compatibility: this is the STAGING deployment
95
- * (`STAGING`), not production. Use `STAGING` / network "staging".
96
- */
97
- const MAINNET = STAGING;
98
- /** Default built-in network. Points at the current staging deployment until production ships. */
99
- const DEFAULT_NETWORK = "staging";
109
+ const MAINNET = PROD;
100
110
  const CONFIGS = {
101
- staging: STAGING,
102
- mainnet: STAGING,
103
- "staging-legacy": STAGING_LEGACY
111
+ mainnet: PROD,
112
+ staging: STAGING
104
113
  };
105
114
  /**
106
115
  * Build a read config from a deployment record instead of the built-in ids, e.g.
107
- * `configFromDeployment(JSON.parse(readFileSync(path, "utf8")), { network: "mainnet", oracleV2 })`.
108
- * The record has no RedStone package or Pyth feed map, so `oracleV2` comes from the caller.
116
+ * `configFromDeployment(JSON.parse(readFileSync(path, "utf8")), { network: "mainnet", oracle })`.
117
+ * The record has no RedStone package or Pyth feed map, so `oracle` comes from the caller.
109
118
  * Throws once, listing every required field the record is missing.
110
119
  */
111
120
  function configFromDeployment(rec, opts) {
@@ -145,16 +154,17 @@ function configFromDeployment(rec, opts) {
145
154
  oracleAuth: obj("oracle", "auth", false),
146
155
  vaultAdmin: obj("vaults", "admin", false)
147
156
  },
148
- oracleVariant: "v2",
149
- oracleV2: opts.oracleV2,
157
+ oracle: opts.oracle,
150
158
  tokens: opts.tokens ?? TOKENS,
151
159
  rpcUrls: opts.rpcUrls,
152
- graphqlUrl: opts.graphqlUrl
160
+ graphqlUrl: opts.graphqlUrl,
161
+ mathVariant: opts.mathVariant ?? (PRE_AUDIT_LIQUIDITY.has(normalizeSuiObjectId(ids("liquidity").packageId)) ? "pre-audit" : "audited")
153
162
  };
154
163
  if (missing.length) throw new Error(`deployment record is missing ${missing.length} required field(s): ${missing.join(", ")}. Package ids alone are not enough; the registry object ids are needed too (see "Deployment record shape" in the README).`);
155
164
  return config;
156
165
  }
157
166
  function resolveConfig(networkOrConfig) {
167
+ if (networkOrConfig === void 0 || networkOrConfig === null || networkOrConfig === "") throw new Error("Fluid network required: pass { network: \"mainnet\" } (production), { network: \"staging\" } (the staging-on-mainnet deployment) or a config from configFromDeployment(...). There is no default.");
158
168
  const cfg = typeof networkOrConfig === "string" ? CONFIGS[networkOrConfig] : networkOrConfig;
159
169
  if (!cfg) throw new Error(`no built-in Fluid config for network "${String(networkOrConfig)}"`);
160
170
  const n = (p) => ({
@@ -187,7 +197,18 @@ const RATE_OUTPUT_DECIMALS = 100000000000000000n;
187
197
  const EXCHANGE_PRICE_SCALE_FACTOR = 1000000000000000000n;
188
198
  /** Lending return precision (1e14 = 1e12 * 100). */
189
199
  const RETURN_PERCENT_PRECISION = 100000000000000n;
190
- /** Lending max rewards rate (50% in 1e12). */
200
+ /**
201
+ * The two raw on-chain rate scales differ, which is why every rate FIELD the SDK returns is bps
202
+ * (1e4 = 100% per year, floored):
203
+ * - liquidity supply rate (`reserve::calculate_exchange_prices`): 1e12 = 100% -> SUPPLY_RATE_PRECISION
204
+ * - lending rewards rate (`rewards`, `lending_calcs`): 1e14 = 100% -> RETURN_PERCENT_PRECISION
205
+ */
206
+ const SUPPLY_RATE_PRECISION = 1000000000000n;
207
+ /** Raw liquidity supply rate (1e12 = 100%) -> bps, floored. */
208
+ const supplyRateToBps = (raw) => raw / (SUPPLY_RATE_PRECISION / FOUR_DECIMALS);
209
+ /** Raw lending rewards rate (1e14 = 100%) -> bps, floored. */
210
+ const rewardsRateToBps = (raw) => raw / (RETURN_PERCENT_PRECISION / FOUR_DECIMALS);
211
+ /** Lending max rewards rate: 50% at RETURN_PERCENT_PRECISION (1e14 = 100%). */
191
212
  const MAX_REWARDS_RATE = 50000000000000n;
192
213
  function mulDiv(a, b, c) {
193
214
  if (c === 0n) throw new RangeError("mulDiv: division by zero");
@@ -206,8 +227,94 @@ const min = (a, b) => a < b ? a : b;
206
227
  /** Current unix time in seconds (the default `now` for time-dependent reads). */
207
228
  const nowSeconds = () => BigInt(Math.floor(Date.now() / 1e3));
208
229
 
230
+ //#endregion
231
+ //#region src/objects.ts
232
+ /** `getObjects` accepts at most 50 ids per call. */
233
+ const BATCH = 50;
234
+ /** Batched fetch. Missing/deleted ids come back as `null` in input order; other errors throw. */
235
+ async function fetchObjects(client, ids) {
236
+ const out = [];
237
+ const chunks = [];
238
+ for (let i = 0; i < ids.length; i += BATCH) chunks.push(ids.slice(i, i + BATCH));
239
+ const pages = await Promise.all(chunks.map((c) => client.core.getObjects({
240
+ objectIds: c,
241
+ include: { content: true }
242
+ })));
243
+ for (const { objects } of pages) for (const o of objects) {
244
+ if (o instanceof Error) {
245
+ if (o instanceof ObjectError && (o.reason === "notFound" || o.reason === "deleted")) {
246
+ out.push(null);
247
+ continue;
248
+ }
249
+ throw o;
250
+ }
251
+ out.push({
252
+ objectId: o.objectId,
253
+ type: o.type,
254
+ content: o.content,
255
+ owner: o.owner
256
+ });
257
+ }
258
+ return out;
259
+ }
260
+ var ObjectNotFoundError = class extends Error {
261
+ constructor(objectId, what) {
262
+ super(`${what} not found: ${objectId}`);
263
+ this.objectId = objectId;
264
+ this.what = what;
265
+ this.name = "ObjectNotFoundError";
266
+ }
267
+ };
268
+ async function fetchObject(client, id, what) {
269
+ const [o] = await fetchObjects(client, [id]);
270
+ if (!o) throw new ObjectNotFoundError(id, what);
271
+ return o;
272
+ }
273
+ /** Every dynamic-field id under a parent (Table), paginated. */
274
+ async function listDynamicFieldIds(client, parentId) {
275
+ const ids = [];
276
+ let cursor = null;
277
+ do {
278
+ const page = await client.core.listDynamicFields({
279
+ parentId,
280
+ cursor,
281
+ limit: 50
282
+ });
283
+ for (const f of page.dynamicFields) ids.push(f.fieldId);
284
+ cursor = page.hasNextPage ? page.cursor : null;
285
+ } while (cursor !== null);
286
+ return ids;
287
+ }
288
+ /** Owner address (or parent object id) from a core-API owner union. */
289
+ function ownerAddress(owner) {
290
+ if (!owner || typeof owner !== "object") return null;
291
+ const o = owner;
292
+ for (const k of [
293
+ "AddressOwner",
294
+ "ObjectOwner",
295
+ "ConsensusAddressOwner"
296
+ ]) {
297
+ const v = o[k];
298
+ if (typeof v === "string") return v;
299
+ if (v && typeof v === "object" && typeof v.owner === "string") return v.owner;
300
+ }
301
+ if (o.$kind === "Shared") return null;
302
+ return null;
303
+ }
304
+
209
305
  //#endregion
210
306
  //#region src/context.ts
307
+ /** The shared `0x2::clock::Clock` object. */
308
+ const CLOCK_ID = "0x0000000000000000000000000000000000000000000000000000000000000006";
309
+ /**
310
+ * Chain time in seconds from the on-chain `Clock` (0x6) as the fullnode last saw it, i.e. the
311
+ * timestamp of its latest executed checkpoint. Same value a Move call would read via
312
+ * `units::from_clock(clock)` (ms / 1000, floored).
313
+ */
314
+ async function chainNowSeconds(client) {
315
+ const o = await fetchObject(client, CLOCK_ID, "Clock");
316
+ return new DataView(o.content.buffer, o.content.byteOffset + 32, 8).getBigUint64(0, true) / 1000n;
317
+ }
211
318
  const RANK = {
212
319
  state: 0,
213
320
  fullnode: 1,
@@ -222,19 +329,42 @@ var NeedsDiscoveryError = class extends Error {
222
329
  this.name = "NeedsDiscoveryError";
223
330
  }
224
331
  };
225
- function makeContext(client, config, derive, now, discovery = "state") {
332
+ function makeContext(client, config, derive, clock = "chain", discovery = "state") {
333
+ const now = clock === "chain" ? () => chainNowSeconds(client) : clock === "local" ? async () => nowSeconds() : async () => clock();
334
+ const metaCache = /* @__PURE__ */ new Map();
335
+ const coinMetadata = (coinType) => {
336
+ let p = metaCache.get(coinType);
337
+ if (!p) {
338
+ p = client.core.getCoinMetadata({ coinType }).then(({ coinMetadata: m }) => m ? {
339
+ name: m.name,
340
+ symbol: m.symbol,
341
+ decimals: m.decimals
342
+ } : null);
343
+ p.catch(() => metaCache.delete(coinType));
344
+ metaCache.set(coinType, p);
345
+ }
346
+ return p;
347
+ };
226
348
  return {
227
349
  client,
228
350
  config,
229
351
  derive,
230
- now: now ?? nowSeconds,
352
+ now,
231
353
  discovery,
232
- allows: (l) => RANK[discovery] >= RANK[l]
354
+ allows: (l) => RANK[discovery] >= RANK[l],
355
+ math: config.mathVariant ?? "audited",
356
+ coinMetadata
233
357
  };
234
358
  }
235
359
 
236
360
  //#endregion
237
361
  //#region src/transport.ts
362
+ const sleep = (ms) => new Promise((r) => setTimeout(r, ms));
363
+ /** Wait before pass `pass` (1-based, pass >= 2): exponential, capped, +/-25% jitter. */
364
+ function backoffDelay(pass, base, cap) {
365
+ const d = Math.min(cap, base * 2 ** (pass - 2));
366
+ return Math.round(d * (.75 + Math.random() * .5));
367
+ }
238
368
  var AllEndpointsFailedError = class extends Error {
239
369
  constructor(method, errors) {
240
370
  super(`all ${errors.length} RPC attempts failed for core.${method}: ` + errors.map((e) => `${e.endpoint}: ${e.error instanceof Error ? e.error.message : String(e.error)}`).join(" | "));
@@ -268,12 +398,10 @@ function createFailoverClient(endpoints, opts) {
268
398
  if (endpoints.length === 0) throw new Error("createFailoverClient: at least one endpoint required");
269
399
  const labels = endpoints.map((e, i) => typeof e === "string" ? e : `client#${i}`);
270
400
  const clients = endpoints.map((e) => typeof e === "string" ? clientForEndpoint(e, opts.network) : e);
271
- if (clients.length === 1) return Object.assign(clients[0], {
272
- endpoints: labels,
273
- preferred: () => labels[0]
274
- });
275
401
  const timeoutMs = opts.timeoutMs ?? 15e3;
276
- const rounds = opts.rounds ?? 2;
402
+ const rounds = opts.rounds ?? 3;
403
+ const backoffMs = opts.backoffMs ?? 250;
404
+ const maxBackoffMs = opts.maxBackoffMs ?? 4e3;
277
405
  let preferred = 0;
278
406
  const core = new Proxy({}, { get(_t, prop) {
279
407
  const first = clients[preferred].core;
@@ -286,6 +414,7 @@ function createFailoverClient(endpoints, opts) {
286
414
  const errors = [];
287
415
  for (let attempt = 0; attempt < clients.length * rounds; attempt++) {
288
416
  const i = (preferred + attempt) % clients.length;
417
+ if (attempt > 0 && attempt % clients.length === 0) await sleep(backoffDelay(attempt / clients.length + 1, backoffMs, maxBackoffMs));
289
418
  const c = clients[i].core;
290
419
  try {
291
420
  const out = await withTimeout(attempt === 0 ? res : c[prop].apply(c, args), timeoutMs, `${labels[i]} core.${String(prop)}`);
@@ -312,6 +441,151 @@ function createFailoverClient(endpoints, opts) {
312
441
  } });
313
442
  }
314
443
 
444
+ //#endregion
445
+ //#region src/graphql.ts
446
+ /** Minimal Sui GraphQL client (fetch), with the same retry + backoff policy as the RPC failover. */
447
+ var GraphQLError = class extends Error {
448
+ constructor(message, errors) {
449
+ super(message);
450
+ this.errors = errors;
451
+ this.name = "GraphQLError";
452
+ }
453
+ };
454
+ /**
455
+ * POST one query. Network errors, timeouts, HTTP 429/5xx are retried with backoff; a GraphQL
456
+ * `errors` payload (bad query, unknown object) is NOT retried.
457
+ */
458
+ async function graphqlQuery(url, query, variables = {}, opts = {}) {
459
+ const rounds = opts.rounds ?? 3;
460
+ const timeoutMs = opts.timeoutMs ?? 15e3;
461
+ let last;
462
+ for (let pass = 1; pass <= rounds; pass++) {
463
+ if (pass > 1) await new Promise((r) => setTimeout(r, backoffDelay(pass, opts.backoffMs ?? 250, opts.maxBackoffMs ?? 4e3)));
464
+ let res;
465
+ try {
466
+ res = await fetch(url, {
467
+ method: "POST",
468
+ headers: { "content-type": "application/json" },
469
+ body: JSON.stringify({
470
+ query,
471
+ variables
472
+ }),
473
+ signal: AbortSignal.timeout(timeoutMs)
474
+ });
475
+ } catch (e) {
476
+ last = e;
477
+ continue;
478
+ }
479
+ if (res.status === 429 || res.status >= 500) {
480
+ last = new GraphQLError(`GraphQL HTTP ${res.status}`);
481
+ continue;
482
+ }
483
+ const body = await res.json();
484
+ if (body.errors?.length) throw new GraphQLError(`GraphQL: ${body.errors.map((e) => e.message).join("; ")}`, body.errors);
485
+ if (!body.data) throw new GraphQLError(`GraphQL HTTP ${res.status}: no data`);
486
+ return body.data;
487
+ }
488
+ throw last instanceof Error ? last : new GraphQLError(String(last));
489
+ }
490
+
491
+ //#endregion
492
+ //#region src/checkpoint.ts
493
+ var CheckpointReadError = class extends Error {
494
+ constructor(method, checkpoint) {
495
+ super(`core.${method} is not available for historical reads (checkpoint ${checkpoint}); only object state is`);
496
+ this.name = "CheckpointReadError";
497
+ }
498
+ };
499
+ /** GraphQL `multiGetObjects` takes at most 50 keys per query. */
500
+ const KEYS_PER_QUERY = 50;
501
+ const OBJECT_FIELDS = `address version digest
502
+ owner { __typename ... on AddressOwner { address { address } } ... on ObjectOwner { address { address } }
503
+ ... on Shared { initialSharedVersion } ... on ConsensusAddressOwner { startVersion address { address } } }
504
+ asMoveObject { contents { type { repr } bcs } }`;
505
+ function coreOwner(o) {
506
+ switch (o?.__typename) {
507
+ case "AddressOwner": return {
508
+ $kind: "AddressOwner",
509
+ AddressOwner: o.address.address
510
+ };
511
+ case "ObjectOwner": return {
512
+ $kind: "ObjectOwner",
513
+ ObjectOwner: o.address.address
514
+ };
515
+ case "Shared": return {
516
+ $kind: "Shared",
517
+ Shared: { initialSharedVersion: String(o.initialSharedVersion) }
518
+ };
519
+ case "Immutable": return {
520
+ $kind: "Immutable",
521
+ Immutable: true
522
+ };
523
+ case "ConsensusAddressOwner": return {
524
+ $kind: "ConsensusAddressOwner",
525
+ ConsensusAddressOwner: {
526
+ startVersion: String(o.startVersion),
527
+ owner: o.address.address
528
+ }
529
+ };
530
+ default: return { $kind: "Unknown" };
531
+ }
532
+ }
533
+ /** Timestamp of `checkpoint` in unix seconds (floored, as `units::from_clock` reads it). */
534
+ async function checkpointTimestampSeconds(url, checkpoint, opts) {
535
+ const d = await graphqlQuery(url, `query($c: UInt53!) { checkpoint(sequenceNumber: $c) { timestamp } }`, { c: Number(checkpoint) }, opts);
536
+ if (!d.checkpoint) throw new Error(`checkpoint ${checkpoint} not found on ${url}`);
537
+ return BigInt(Math.floor(Date.parse(d.checkpoint.timestamp) / 1e3));
538
+ }
539
+ /** Latest checkpoint the GraphQL service has indexed. */
540
+ async function latestCheckpoint(url, opts) {
541
+ const d = await graphqlQuery(url, `{ checkpoint { sequenceNumber } }`, {}, opts);
542
+ return BigInt(d.checkpoint.sequenceNumber);
543
+ }
544
+ /**
545
+ * A core client pinned to `checkpoint`: `getObjects` via GraphQL at that checkpoint,
546
+ * `getCoinMetadata` from `live` (immutable data), every other core read throws.
547
+ */
548
+ function checkpointClient(url, checkpoint, live, opts) {
549
+ const getObjects = async ({ objectIds }) => {
550
+ const objects = [];
551
+ for (let i = 0; i < objectIds.length; i += KEYS_PER_QUERY) {
552
+ const ids = objectIds.slice(i, i + KEYS_PER_QUERY);
553
+ (await graphqlQuery(url, `query($k: [ObjectKey!]!) { multiGetObjects(keys: $k) { ${OBJECT_FIELDS} } }`, { k: ids.map((address) => ({
554
+ address,
555
+ atCheckpoint: Number(checkpoint)
556
+ })) }, opts)).multiGetObjects.forEach((o, j) => {
557
+ if (!o?.asMoveObject) {
558
+ objects.push(new ObjectError("notFound", `object ${ids[j]} not found at checkpoint ${checkpoint}`, {
559
+ reason: "notFound",
560
+ objectId: ids[j]
561
+ }));
562
+ return;
563
+ }
564
+ objects.push({
565
+ objectId: o.address,
566
+ version: String(o.version),
567
+ digest: o.digest ?? "",
568
+ type: o.asMoveObject.contents.type.repr,
569
+ owner: coreOwner(o.owner),
570
+ content: fromBase64(o.asMoveObject.contents.bcs)
571
+ });
572
+ });
573
+ }
574
+ return { objects };
575
+ };
576
+ return {
577
+ core: new Proxy({}, { get(_t, prop) {
578
+ if (prop === "getObjects") return getObjects;
579
+ if (prop === "getCoinMetadata") return live.core.getCoinMetadata.bind(live.core);
580
+ if (typeof prop === "symbol" || prop === "then") return void 0;
581
+ return () => {
582
+ throw new CheckpointReadError(String(prop), checkpoint);
583
+ };
584
+ } }),
585
+ checkpoint
586
+ };
587
+ }
588
+
315
589
  //#endregion
316
590
  //#region src/bcs.ts
317
591
  /**
@@ -321,7 +595,6 @@ function createFailoverClient(endpoints, opts) {
321
595
  * Single-field wrappers (UID, ID, TimestampS, DurationS, Balance, Supply) encode as their field.
322
596
  */
323
597
  var bcs_exports = /* @__PURE__ */ __exportAll({
324
- AdminOracle: () => AdminOracle,
325
598
  Branch: () => Branch,
326
599
  BranchStatus: () => BranchStatus,
327
600
  CapRecord: () => CapRecord,
@@ -331,17 +604,16 @@ var bcs_exports = /* @__PURE__ */ __exportAll({
331
604
  LendingState: () => LendingState,
332
605
  LiquidityRegistry: () => LiquidityRegistry,
333
606
  MoveTick: () => MoveTick,
607
+ OracleObject: () => OracleObject,
334
608
  OracleRegistry: () => OracleRegistry,
335
609
  OracleSource: () => OracleSource,
336
610
  OracleSourceType: () => OracleSourceType,
337
- OracleV2: () => OracleV2,
338
611
  PauseFlags: () => PauseFlags,
339
612
  Position: () => Position,
340
613
  PositionIndex: () => PositionIndex$1,
341
614
  PositionIndexEntry: () => PositionIndexEntry,
342
615
  PositionIndexField: () => PositionIndexField,
343
616
  PriceQuote: () => PriceQuote,
344
- PriceQuoteV2: () => PriceQuoteV2,
345
617
  PythPriceInfoObject: () => PythPriceInfoObject,
346
618
  RateDataV1Params: () => RateDataV1Params,
347
619
  RateDataV2Params: () => RateDataV2Params,
@@ -574,22 +846,11 @@ const VaultRegistry = bcs.struct("VaultRegistry", {
574
846
  next_vault_id: U64,
575
847
  version: U64
576
848
  });
577
- const AdminOracle = bcs.struct("Oracle", {
578
- id: Address,
579
- name: bcs.string(),
580
- rate: U128,
581
- version: U64
582
- });
583
849
  const OracleRegistry = bcs.struct("OracleRegistry", {
584
850
  id: Address,
585
851
  version: U64,
586
852
  total_oracles: U64
587
853
  });
588
- const PriceQuote = bcs.struct("PriceQuote", {
589
- oracle_id: Address,
590
- rate_liquidate: U128,
591
- rate_operate: U128
592
- });
593
854
  const OracleSourceType = bcs.enum("SourceType", {
594
855
  Redstone: bcs.struct("Redstone", { adapter_id: Address }),
595
856
  Pyth: null
@@ -599,15 +860,15 @@ const OracleSource = bcs.struct("Source", {
599
860
  invert: bcs.bool(),
600
861
  source_type: OracleSourceType
601
862
  });
602
- /** `fluid_oracle::oracle::Oracle` (v2): derived from the registry by nonce. */
603
- const OracleV2 = bcs.struct("Oracle", {
863
+ /** `fluid_oracle::oracle::Oracle` derived from the registry by nonce. */
864
+ const OracleObject = bcs.struct("Oracle", {
604
865
  id: Address,
605
866
  nonce: bcs.u16(),
606
867
  main: OracleSource,
607
868
  guard: bcs.option(OracleSource)
608
869
  });
609
- /** v2 `view::PriceQuote`: each rate is optional (operate-only / liquidate-only quotes). */
610
- const PriceQuoteV2 = bcs.struct("PriceQuote", {
870
+ /** `view::PriceQuote`: each rate is optional (operate-only / liquidate-only quotes). */
871
+ const PriceQuote = bcs.struct("PriceQuote", {
611
872
  oracle_id: Address,
612
873
  rate_liquidate: bcs.option(U128),
613
874
  rate_operate: bcs.option(U128)
@@ -816,6 +1077,8 @@ var liquidity_exports = /* @__PURE__ */ __exportAll({
816
1077
  processOverallTokenData: () => processOverallTokenData,
817
1078
  processUserBorrowData: () => processUserBorrowData,
818
1079
  processUserSupplyData: () => processUserSupplyData,
1080
+ supplyRateAt: () => supplyRateAt,
1081
+ supplyRateRaw: () => supplyRateRaw,
819
1082
  totalAmountsOf: () => totalAmountsOf,
820
1083
  totalBorrowNormal: () => totalBorrowNormal,
821
1084
  totalSupplyNormal: () => totalSupplyNormal
@@ -825,8 +1088,31 @@ function withInterestVsFreeRatio(withInterest, interestFree) {
825
1088
  if (withInterest < interestFree) return withInterest * FOUR_DECIMALS / interestFree;
826
1089
  return withInterest > 0n ? FOUR_DECIMALS : 0n;
827
1090
  }
1091
+ /**
1092
+ * Supply rate the reserve credits for a period starting from these exchange prices, at
1093
+ * `SUPPLY_RATE_PRECISION` (1e12 = 100% per year): exactly `borrow_rate * ratio_supply_yield * (1e4 - fee)`
1094
+ * from `reserve::calculate_exchange_prices` (interest-free ratios on both sides, fee applied).
1095
+ * `borrowEpForRatio` is the borrow exchange price the contract normalises the borrow ratio with.
1096
+ */
1097
+ function supplyRateRaw(r, supplyExchangePrice, borrowEpForRatio, variant) {
1098
+ if (r.borrowRate === 0n || r.totalBorrowWithInterest === 0n || r.totalSupplyWithInterest === 0n) return 0n;
1099
+ const supplyWithInterestNormal = divCeil(r.totalSupplyWithInterest * supplyExchangePrice, EXCHANGE_PRICES_PRECISION);
1100
+ const supplyRatio = withInterestVsFreeRatio(supplyWithInterestNormal, r.totalSupplyInterestFree);
1101
+ let ratioSupplyYield;
1102
+ if (supplyWithInterestNormal < r.totalSupplyInterestFree) {
1103
+ if (supplyRatio === 0n) return 0n;
1104
+ const inverted = variant === "audited" ? mulDiv(r.totalSupplyInterestFree, RATE_OUTPUT_DECIMALS, supplyWithInterestNormal) : RATE_OUTPUT_DECIMALS * FOUR_DECIMALS / supplyRatio;
1105
+ ratioSupplyYield = r.lastUtilization * (RATE_OUTPUT_DECIMALS + inverted) / FOUR_DECIMALS;
1106
+ } else ratioSupplyYield = r.lastUtilization * RATE_OUTPUT_DECIMALS * (FOUR_DECIMALS + supplyRatio) / (FOUR_DECIMALS * FOUR_DECIMALS);
1107
+ const borrowWithInterestNormal = r.totalBorrowWithInterest * borrowEpForRatio / EXCHANGE_PRICES_PRECISION;
1108
+ let borrowRatio = withInterestVsFreeRatio(borrowWithInterestNormal, r.totalBorrowInterestFree);
1109
+ borrowRatio = borrowWithInterestNormal < r.totalBorrowInterestFree ? borrowRatio * RATE_OUTPUT_DECIMALS / (FOUR_DECIMALS + borrowRatio) : RATE_OUTPUT_DECIMALS - borrowRatio * RATE_OUTPUT_DECIMALS / (FOUR_DECIMALS + borrowRatio);
1110
+ ratioSupplyYield = mulDiv(ratioSupplyYield, borrowRatio, RATE_OUTPUT_DECIMALS);
1111
+ ratioSupplyYield = ratioSupplyYield * FOUR_DECIMALS / RATE_OUTPUT_DECIMALS;
1112
+ return r.borrowRate * ratioSupplyYield * (FOUR_DECIMALS - r.fee);
1113
+ }
828
1114
  /** `reserve::calculate_exchange_prices(now)`. */
829
- function calculateExchangePrice(r, now) {
1115
+ function calculateExchangePrice(r, now, variant = "audited") {
830
1116
  let supplyExchangePrice = r.supplyExchangePrice;
831
1117
  let borrowExchangePrice = r.borrowExchangePrice;
832
1118
  if (supplyExchangePrice === 0n || borrowExchangePrice === 0n) throw new RangeError("exchange price zero (reserve never initialised)");
@@ -835,34 +1121,23 @@ function calculateExchangePrice(r, now) {
835
1121
  supplyExchangePrice,
836
1122
  borrowExchangePrice
837
1123
  };
1124
+ const borrowBefore = borrowExchangePrice;
838
1125
  borrowExchangePrice += divCeil(borrowExchangePrice * r.borrowRate * elapsed, SECONDS_PER_YEAR * FOUR_DECIMALS);
839
- if (r.totalSupplyWithInterest === 0n) return {
840
- supplyExchangePrice,
841
- borrowExchangePrice
842
- };
843
- const supplyWithInterestNormal = divCeil(r.totalSupplyWithInterest * supplyExchangePrice, EXCHANGE_PRICES_PRECISION);
844
- let supplyRatio = withInterestVsFreeRatio(supplyWithInterestNormal, r.totalSupplyInterestFree);
845
- let ratioSupplyYield;
846
- if (supplyWithInterestNormal < r.totalSupplyInterestFree) {
847
- if (supplyRatio === 0n) return {
848
- supplyExchangePrice,
849
- borrowExchangePrice
850
- };
851
- supplyRatio = RATE_OUTPUT_DECIMALS * FOUR_DECIMALS / supplyRatio;
852
- ratioSupplyYield = r.lastUtilization * (RATE_OUTPUT_DECIMALS + supplyRatio) / FOUR_DECIMALS;
853
- } else ratioSupplyYield = r.lastUtilization * RATE_OUTPUT_DECIMALS * (FOUR_DECIMALS + supplyRatio) / (FOUR_DECIMALS * FOUR_DECIMALS);
854
- const borrowWithInterestNormal = r.totalBorrowWithInterest * borrowExchangePrice / EXCHANGE_PRICES_PRECISION;
855
- let borrowRatio = withInterestVsFreeRatio(borrowWithInterestNormal, r.totalBorrowInterestFree);
856
- borrowRatio = borrowWithInterestNormal < r.totalBorrowInterestFree ? borrowRatio * RATE_OUTPUT_DECIMALS / (FOUR_DECIMALS + borrowRatio) : RATE_OUTPUT_DECIMALS - borrowRatio * RATE_OUTPUT_DECIMALS / (FOUR_DECIMALS + borrowRatio);
857
- ratioSupplyYield = mulDiv(ratioSupplyYield, borrowRatio, RATE_OUTPUT_DECIMALS);
858
- ratioSupplyYield = ratioSupplyYield * FOUR_DECIMALS / RATE_OUTPUT_DECIMALS;
859
- const supplyRate = r.borrowRate * ratioSupplyYield * (FOUR_DECIMALS - r.fee);
860
- supplyExchangePrice += supplyExchangePrice * supplyRate * elapsed / (SECONDS_PER_YEAR * FOUR_DECIMALS) / (FOUR_DECIMALS * FOUR_DECIMALS);
1126
+ const rate = supplyRateRaw(r, supplyExchangePrice, variant === "audited" ? borrowBefore : borrowExchangePrice, variant);
1127
+ supplyExchangePrice += supplyExchangePrice * rate * elapsed / (SECONDS_PER_YEAR * FOUR_DECIMALS) / (FOUR_DECIMALS * FOUR_DECIMALS);
861
1128
  return {
862
1129
  supplyExchangePrice,
863
1130
  borrowExchangePrice
864
1131
  };
865
1132
  }
1133
+ /**
1134
+ * Instantaneous liquidity supply rate at `now` (1e12 = 100%): the rate the contract would credit
1135
+ * for the period starting at `now`, i.e. `supplyRateRaw` on both exchange prices accrued to `now`.
1136
+ */
1137
+ function supplyRateAt(r, now, variant = "audited") {
1138
+ const ep = calculateExchangePrice(r, now, variant);
1139
+ return supplyRateRaw(r, ep.supplyExchangePrice, ep.borrowExchangePrice, variant);
1140
+ }
866
1141
  /** `liquidity_calcs::get_total_supply` (floor). */
867
1142
  const totalSupplyNormal = (r, supplyEp) => r.totalSupplyInterestFree + r.totalSupplyWithInterest * supplyEp / EXCHANGE_PRICES_PRECISION;
868
1143
  /** `liquidity_calcs::get_total_borrow` (floor). */
@@ -883,15 +1158,13 @@ function totalAmountsOf(r) {
883
1158
  totalClaimAmount: 0n
884
1159
  };
885
1160
  }
886
- /** Same shape/semantics as the Solana reader's `processOverallTokenData`. */
887
- function processOverallTokenData(r, rateData, now) {
888
- const { supplyExchangePrice, borrowExchangePrice } = calculateExchangePrice(r, now);
889
- let supplyRate = 0n;
890
- if (r.totalSupplyWithInterest !== 0n) {
891
- const borrowForRate = r.totalBorrowWithInterest * r.borrowExchangePrice / EXCHANGE_PRICES_PRECISION;
892
- const supplyForRate = r.totalSupplyWithInterest * r.supplyExchangePrice / EXCHANGE_PRICES_PRECISION;
893
- if (supplyForRate !== 0n) supplyRate = r.borrowRate * (FOUR_DECIMALS - r.fee) * borrowForRate / (supplyForRate * FOUR_DECIMALS);
894
- }
1161
+ /**
1162
+ * Same shape as the Solana reader's `processOverallTokenData`. `supplyRate` (bps) is the exact
1163
+ * on-chain supply rate at `now` (`supplyRateAt`, floored to bps), not the Solana approximation.
1164
+ */
1165
+ function processOverallTokenData(r, rateData, now, variant = "audited") {
1166
+ const { supplyExchangePrice, borrowExchangePrice } = calculateExchangePrice(r, now, variant);
1167
+ const supplyRate = supplyRateToBps(supplyRateRaw(r, supplyExchangePrice, borrowExchangePrice, variant));
895
1168
  return {
896
1169
  rateData,
897
1170
  supplyExchangePrice,
@@ -1035,6 +1308,7 @@ var lending_exports = /* @__PURE__ */ __exportAll({
1035
1308
  previews: () => previews,
1036
1309
  rateSegments: () => rateSegments,
1037
1310
  rewardsRateFor: () => rewardsRateFor,
1311
+ rewardsTvl: () => rewardsTvl,
1038
1312
  toAssets: () => toAssets,
1039
1313
  toShares: () => toShares
1040
1314
  });
@@ -1062,30 +1336,40 @@ function rateSegments(s, totalAssets, fromS, nowS) {
1062
1336
  }
1063
1337
  const maxB = (a, b) => a > b ? a : b;
1064
1338
  const minB = (a, b) => a < b ? a : b;
1065
- /** Current rewards rate (1e12 = 100%) at `now`, from the active schedule segment. */
1066
- function currentRewardsRate(state, now) {
1339
+ /**
1340
+ * TVL the contract prices rewards on: total shares at the STORED (pre-accrual) token exchange
1341
+ * price, as `accounting::accrued_token_exchange_price` does. Rounded up on "audited" packages
1342
+ * (742ea21+), floored on the 09-18 "pre-audit" packages.
1343
+ */
1344
+ const rewardsTvl = (state, variant = "audited") => toAssets(state.totalShares, state.tokenExchangePrice, variant === "audited");
1345
+ /**
1346
+ * Current rewards rate (raw, RETURN_PERCENT_PRECISION: 1e14 = 100%) at `now`, from the active schedule segment, priced on
1347
+ * the pre-accrual TVL (`rewardsTvl`) exactly as the contract prices the segment it will credit.
1348
+ * Windows are [start, end): the rate is live from `startS` and gone at `endS`.
1349
+ */
1350
+ function currentRewardsRate(state, now, variant = "audited") {
1067
1351
  if (!state.rewards) return 0n;
1068
- const tvl = toAssets(state.totalShares, state.tokenExchangePrice, false);
1352
+ const tvl = rewardsTvl(state, variant);
1069
1353
  const s = state.rewards;
1070
1354
  if (now >= s.startS && now < s.endS) return rewardsRateFor(s.yearlyReward, tvl, s.startTvl);
1071
1355
  if (now >= s.nextStartS && now < s.nextEndS) return rewardsRateFor(s.nextYearlyReward, tvl, s.startTvl);
1072
1356
  return 0n;
1073
1357
  }
1074
1358
  /** `accounting::accrued_token_exchange_price`. */
1075
- function accruedTokenExchangePrice(state, newLiquidityExchangePrice, nowS) {
1359
+ function accruedTokenExchangePrice(state, newLiquidityExchangePrice, nowS, variant = "audited") {
1076
1360
  const oldLiq = state.liquidityExchangePrice;
1077
1361
  const oldToken = state.tokenExchangePrice;
1078
1362
  if (newLiquidityExchangePrice < oldLiq) throw new RangeError("liquidity exchange price went backwards");
1079
1363
  let totalReturn = mulDiv(newLiquidityExchangePrice - oldLiq, RETURN_PERCENT_PRECISION, oldLiq);
1080
1364
  if (state.rewards) {
1081
- const tvl = toAssets(state.totalShares, oldToken, false);
1365
+ const tvl = rewardsTvl(state, variant);
1082
1366
  for (const seg of rateSegments(state.rewards, tvl, state.lastUpdateS, nowS)) totalReturn += mulDiv(seg.rate, seg.toS - seg.fromS, SECONDS_PER_YEAR);
1083
1367
  }
1084
1368
  return oldToken + mulDiv(oldToken, totalReturn, RETURN_PERCENT_PRECISION);
1085
1369
  }
1086
1370
  /** `accounting::preview_*` given the reserve's current liquidity (supply) exchange price. */
1087
- function previews(state, liquidityExchangePrice, nowS, assets, shares) {
1088
- const tokenEp = accruedTokenExchangePrice(state, liquidityExchangePrice, nowS);
1371
+ function previews(state, liquidityExchangePrice, nowS, assets, shares, variant = "audited") {
1372
+ const tokenEp = accruedTokenExchangePrice(state, liquidityExchangePrice, nowS, variant);
1089
1373
  return {
1090
1374
  tokenExchangePrice: tokenEp,
1091
1375
  previewDeposit: toShares(toAssets(toShares(assets, liquidityExchangePrice, false), liquidityExchangePrice, false), tokenEp, false),
@@ -1095,81 +1379,6 @@ function previews(state, liquidityExchangePrice, nowS, assets, shares) {
1095
1379
  };
1096
1380
  }
1097
1381
 
1098
- //#endregion
1099
- //#region src/objects.ts
1100
- /** `getObjects` accepts at most 50 ids per call. */
1101
- const BATCH = 50;
1102
- /** Batched fetch. Missing/deleted ids come back as `null` in input order; other errors throw. */
1103
- async function fetchObjects(client, ids) {
1104
- const out = [];
1105
- const chunks = [];
1106
- for (let i = 0; i < ids.length; i += BATCH) chunks.push(ids.slice(i, i + BATCH));
1107
- const pages = await Promise.all(chunks.map((c) => client.core.getObjects({
1108
- objectIds: c,
1109
- include: { content: true }
1110
- })));
1111
- for (const { objects } of pages) for (const o of objects) {
1112
- if (o instanceof Error) {
1113
- if (o instanceof ObjectError && (o.reason === "notFound" || o.reason === "deleted")) {
1114
- out.push(null);
1115
- continue;
1116
- }
1117
- throw o;
1118
- }
1119
- out.push({
1120
- objectId: o.objectId,
1121
- type: o.type,
1122
- content: o.content,
1123
- owner: o.owner
1124
- });
1125
- }
1126
- return out;
1127
- }
1128
- var ObjectNotFoundError = class extends Error {
1129
- constructor(objectId, what) {
1130
- super(`${what} not found: ${objectId}`);
1131
- this.objectId = objectId;
1132
- this.what = what;
1133
- this.name = "ObjectNotFoundError";
1134
- }
1135
- };
1136
- async function fetchObject(client, id, what) {
1137
- const [o] = await fetchObjects(client, [id]);
1138
- if (!o) throw new ObjectNotFoundError(id, what);
1139
- return o;
1140
- }
1141
- /** Every dynamic-field id under a parent (Table), paginated. */
1142
- async function listDynamicFieldIds(client, parentId) {
1143
- const ids = [];
1144
- let cursor = null;
1145
- do {
1146
- const page = await client.core.listDynamicFields({
1147
- parentId,
1148
- cursor,
1149
- limit: 50
1150
- });
1151
- for (const f of page.dynamicFields) ids.push(f.fieldId);
1152
- cursor = page.hasNextPage ? page.cursor : null;
1153
- } while (cursor !== null);
1154
- return ids;
1155
- }
1156
- /** Owner address (or parent object id) from a core-API owner union. */
1157
- function ownerAddress(owner) {
1158
- if (!owner || typeof owner !== "object") return null;
1159
- const o = owner;
1160
- for (const k of [
1161
- "AddressOwner",
1162
- "ObjectOwner",
1163
- "ConsensusAddressOwner"
1164
- ]) {
1165
- const v = o[k];
1166
- if (typeof v === "string") return v;
1167
- if (v && typeof v === "object" && typeof v.owner === "string") return v.owner;
1168
- }
1169
- if (o.$kind === "Shared") return null;
1170
- return null;
1171
- }
1172
-
1173
1382
  //#endregion
1174
1383
  //#region src/view.ts
1175
1384
  /** Any address works as the simulated sender; nothing is executed. */
@@ -1214,12 +1423,18 @@ var Lending = class {
1214
1423
  this.ctx = ctx;
1215
1424
  this.liquidity = liquidity;
1216
1425
  }
1426
+ /** Object ids `loadLendings` reads: every LendingState, then every rewards field. */
1427
+ lendingObjectIds(tokens) {
1428
+ const ids = tokens.map((t) => this.ctx.derive.lendingStateId(normalizeStructTag(t)));
1429
+ return [...ids, ...ids.map((id) => this.ctx.derive.rewardsId(id))];
1430
+ }
1217
1431
  /** LendingStates (+ rewards fields) for `tokens`; `null` where no pool exists. */
1218
1432
  async loadLendings(tokens) {
1219
- const d = this.ctx.derive;
1433
+ return this.lendingsFromObjects(tokens, await fetchObjects(this.ctx.client, this.lendingObjectIds(tokens)));
1434
+ }
1435
+ lendingsFromObjects(tokens, objs) {
1220
1436
  const norm = tokens.map((t) => normalizeStructTag(t));
1221
- const ids = norm.map((t) => d.lendingStateId(t));
1222
- const objs = await fetchObjects(this.ctx.client, [...ids, ...ids.map((id) => d.rewardsId(id))]);
1437
+ const ids = norm.map((t) => this.ctx.derive.lendingStateId(t));
1223
1438
  return norm.map((token, i) => {
1224
1439
  const o = objs[i];
1225
1440
  if (!o) return null;
@@ -1248,23 +1463,54 @@ var Lending = class {
1248
1463
  };
1249
1464
  });
1250
1465
  }
1466
+ /**
1467
+ * Everything a pool read needs in ONE `getObjects` batch (LendingState, rewards field,
1468
+ * TokenReserve, rate data per token; 50 ids per request, chunks in parallel), plus the chain
1469
+ * clock in parallel. With `withEntries`, a second batch reads each pool's liquidity supply entry.
1470
+ */
1471
+ async loadPools(tokens, withEntries = false) {
1472
+ const norm = tokens.map((t) => normalizeStructTag(t));
1473
+ const lIds = this.lendingObjectIds(norm);
1474
+ const [objs, now] = await Promise.all([fetchObjects(this.ctx.client, [...lIds, ...this.liquidity.reserveObjectIds(norm)]), this.ctx.now()]);
1475
+ const lendings = this.lendingsFromObjects(norm, objs.slice(0, lIds.length));
1476
+ const reserves = this.liquidity.reservesFromObjects(norm, objs.slice(lIds.length));
1477
+ const pools = norm.map((token, i) => {
1478
+ const lending = lendings[i];
1479
+ if (!lending) throw new Error(`no LendingState for ${token}`);
1480
+ return {
1481
+ lending,
1482
+ reserve: reserves[i],
1483
+ supplyEntry: null
1484
+ };
1485
+ });
1486
+ if (withEntries) {
1487
+ const entries = await this.liquidity.loadUserEntries(pools.map((p) => ({
1488
+ holder: p.lending.id,
1489
+ reserve: p.reserve
1490
+ })));
1491
+ pools.forEach((p, i) => p.supplyEntry = entries[i].supply);
1492
+ }
1493
+ return {
1494
+ pools,
1495
+ now
1496
+ };
1497
+ }
1251
1498
  async loadOne(token) {
1252
- const [[lending], reserve] = await Promise.all([this.loadLendings([token]), this.liquidity.loadReserve(token)]);
1253
- if (!lending) throw new Error(`no LendingState for ${token}`);
1499
+ const { pools, now } = await this.loadPools([token]);
1254
1500
  return {
1255
- lending,
1256
- reserve
1501
+ ...pools[0],
1502
+ now
1257
1503
  };
1258
1504
  }
1259
1505
  async getAllLendings() {
1260
1506
  const tokens = await this.liquidity.listedTokens();
1261
1507
  const recs = (await this.loadLendings(tokens)).filter((r) => r !== null);
1262
- const metas = await Promise.all(recs.map((r) => this.ctx.client.core.getCoinMetadata({ coinType: r.token })));
1508
+ const metas = await Promise.all(recs.map((r) => this.ctx.coinMetadata(r.token)));
1263
1509
  return recs.map((r, i) => ({
1264
1510
  id: r.id,
1265
1511
  mint: r.token,
1266
1512
  fTokenMint: this.ctx.derive.fTokenType(r.token),
1267
- decimals: metas[i].coinMetadata?.decimals ?? 0,
1513
+ decimals: metas[i]?.decimals ?? 0,
1268
1514
  totalShares: r.snapshot.totalShares,
1269
1515
  liquidityExchangePrice: r.snapshot.liquidityExchangePrice,
1270
1516
  tokenExchangePrice: r.snapshot.tokenExchangePrice,
@@ -1281,16 +1527,14 @@ var Lending = class {
1281
1527
  getAllJlTokens = () => this.getAllFTokens();
1282
1528
  /** Token exchange price accrued to now (`accounting::calculate_new_token_exchange_price`). */
1283
1529
  async getExchangePrice(token) {
1284
- const { lending, reserve } = await this.loadOne(token);
1285
- const now = this.ctx.now();
1286
- const liqEp = calculateExchangePrice(reserve.snapshot, now).supplyExchangePrice;
1287
- return accruedTokenExchangePrice(lending.snapshot, liqEp, now);
1530
+ const { lending, reserve, now } = await this.loadOne(token);
1531
+ const liqEp = calculateExchangePrice(reserve.snapshot, now, this.ctx.math).supplyExchangePrice;
1532
+ return accruedTokenExchangePrice(lending.snapshot, liqEp, now, this.ctx.math);
1288
1533
  }
1289
1534
  async getPreviews(token, assets, shares) {
1290
- const { lending, reserve } = await this.loadOne(token);
1291
- const now = this.ctx.now();
1292
- const liqEp = calculateExchangePrice(reserve.snapshot, now).supplyExchangePrice;
1293
- const p = previews(lending.snapshot, liqEp, now, assets, shares);
1535
+ const { lending, reserve, now } = await this.loadOne(token);
1536
+ const liqEp = calculateExchangePrice(reserve.snapshot, now, this.ctx.math).supplyExchangePrice;
1537
+ const p = previews(lending.snapshot, liqEp, now, assets, shares, this.ctx.math);
1294
1538
  return {
1295
1539
  previewDeposit: p.previewDeposit,
1296
1540
  previewMint: p.previewMint,
@@ -1298,11 +1542,13 @@ var Lending = class {
1298
1542
  previewRedeem: p.previewRedeem
1299
1543
  };
1300
1544
  }
1301
- detailsFromRecords(lending, reserve, supplyEntry, meta, now = this.ctx.now()) {
1545
+ detailsFromRecords(lending, reserve, supplyEntry, meta, now) {
1302
1546
  const { userSupplyData, overallTokenData } = this.liquidity.supplyDataFromRecords(reserve, supplyEntry, now);
1303
- const tokenEp = accruedTokenExchangePrice(lending.snapshot, overallTokenData.supplyExchangePrice, now);
1547
+ const tokenEp = accruedTokenExchangePrice(lending.snapshot, overallTokenData.supplyExchangePrice, now, this.ctx.math);
1304
1548
  const totalAssets = toAssets(lending.snapshot.totalShares, tokenEp, false);
1305
1549
  const one = 10n ** BigInt(meta.decimals);
1550
+ const supplyRate = supplyRateToBps(supplyRateRaw(reserve.snapshot, overallTokenData.supplyExchangePrice, overallTokenData.borrowExchangePrice, this.ctx.math));
1551
+ const rewardsRate = rewardsRateToBps(currentRewardsRate(lending.snapshot, now, this.ctx.math));
1306
1552
  return {
1307
1553
  tokenAddress: this.ctx.derive.fTokenType(lending.token),
1308
1554
  lendingStateId: lending.id,
@@ -1314,60 +1560,207 @@ var Lending = class {
1314
1560
  totalSupply: lending.snapshot.totalShares,
1315
1561
  conversionRateToShares: toShares(one, tokenEp, false),
1316
1562
  conversionRateToAssets: toAssets(one, tokenEp, false),
1317
- rewardsRate: currentRewardsRate(lending.snapshot, now),
1318
- supplyRate: overallTokenData.supplyRate,
1563
+ rewardsRate,
1564
+ supplyRate,
1565
+ totalRate: supplyRate + rewardsRate,
1319
1566
  rebalanceDifference: userSupplyData.supply - totalAssets,
1320
1567
  userSupplyData
1321
1568
  };
1322
1569
  }
1570
+ /** fToken name/symbol/decimals (cached; falls back to the underlying's metadata). */
1323
1571
  async fTokenMeta(token) {
1324
- const [f, u] = await Promise.all([this.ctx.client.core.getCoinMetadata({ coinType: this.ctx.derive.fTokenType(token) }).catch(() => ({ coinMetadata: null })), this.ctx.client.core.getCoinMetadata({ coinType: token })]);
1325
- const under = u.coinMetadata;
1572
+ const [f, under] = await Promise.all([this.ctx.coinMetadata(this.ctx.derive.fTokenType(token)).catch(() => null), this.ctx.coinMetadata(token)]);
1326
1573
  return {
1327
- name: f.coinMetadata?.name ?? `Fluid ${under?.name ?? ""}`.trim(),
1328
- symbol: f.coinMetadata?.symbol ?? `f${under?.symbol ?? ""}`,
1329
- decimals: f.coinMetadata?.decimals ?? under?.decimals ?? 0
1574
+ name: f?.name ?? `Fluid ${under?.name ?? ""}`.trim(),
1575
+ symbol: f?.symbol ?? `f${under?.symbol ?? ""}`,
1576
+ decimals: f?.decimals ?? under?.decimals ?? 0
1330
1577
  };
1331
1578
  }
1332
1579
  async getFTokenDetails(token) {
1333
- const { lending, reserve } = await this.loadOne(token);
1334
- const [[entry], meta] = await Promise.all([this.liquidity.loadUserEntries([{
1335
- holder: lending.id,
1336
- reserve
1337
- }]), this.fTokenMeta(lending.token)]);
1338
- return this.detailsFromRecords(lending, reserve, entry.supply, meta);
1580
+ return (await this.getFTokensDetails([token]))[0];
1581
+ }
1582
+ /** Details for many fTokens: 2 batched object reads + the clock, metadata cached. */
1583
+ async getFTokensDetails(tokens) {
1584
+ return (await this.detailsWithPrices(tokens)).map((d) => d.details);
1585
+ }
1586
+ async detailsWithPrices(tokens) {
1587
+ const [{ pools, now }, metas] = await Promise.all([this.loadPools(tokens, true), Promise.all(tokens.map((t) => this.fTokenMeta(normalizeStructTag(t))))]);
1588
+ return pools.map((p, i) => {
1589
+ const details = this.detailsFromRecords(p.lending, p.reserve, p.supplyEntry, metas[i], now);
1590
+ const liqEp = calculateExchangePrice(p.reserve.snapshot, now, this.ctx.math).supplyExchangePrice;
1591
+ return {
1592
+ details,
1593
+ tokenEp: accruedTokenExchangePrice(p.lending.snapshot, liqEp, now, this.ctx.math)
1594
+ };
1595
+ });
1339
1596
  }
1340
1597
  /** @deprecated Solana name; use `getFTokenDetails`. */
1341
1598
  getJlTokenDetails = (token) => this.getFTokenDetails(token);
1342
1599
  async getAllFTokenDetails() {
1343
- return Promise.all((await this.getAllFTokens()).map((t) => this.getFTokenDetails(t)));
1600
+ return this.getFTokensDetails(await this.listedLendingTokens());
1601
+ }
1602
+ /** Underlying coin types that have a lending pool (pure state: one batched existence read). */
1603
+ async listedLendingTokens() {
1604
+ const tokens = await this.liquidity.listedTokens();
1605
+ const ids = tokens.map((t) => this.ctx.derive.lendingStateId(normalizeStructTag(t)));
1606
+ const objs = await fetchObjects(this.ctx.client, ids);
1607
+ return tokens.map((t) => normalizeStructTag(t)).filter((_, i) => objs[i] !== null);
1344
1608
  }
1345
1609
  /** @deprecated Solana name; use `getAllFTokenDetails`. */
1346
1610
  getAllJlTokenDetails = () => this.getAllFTokenDetails();
1347
1611
  async getFTokenInternalData(token) {
1348
- const { lending, reserve } = await this.loadOne(token);
1349
- const [entry] = await this.liquidity.loadUserEntries([{
1350
- holder: lending.id,
1351
- reserve
1352
- }]);
1353
- const now = this.ctx.now();
1612
+ const { pools, now } = await this.loadPools([token], true);
1613
+ const { lending, reserve, supplyEntry } = pools[0];
1614
+ const entry = { supply: supplyEntry };
1354
1615
  const { userSupplyData } = this.liquidity.supplyDataFromRecords(reserve, entry.supply, now);
1355
- const liqEp = calculateExchangePrice(reserve.snapshot, now).supplyExchangePrice;
1616
+ const liqEp = calculateExchangePrice(reserve.snapshot, now, this.ctx.math).supplyExchangePrice;
1356
1617
  return {
1357
1618
  liquidity: reserve.id,
1358
1619
  lendingFactory: this.ctx.config.objects.lendingRegistry,
1359
1620
  lendingRewardsRateModel: lending.raw.rewards_writer ?? null,
1360
- rebalancer: "",
1361
1621
  liquidityBalance: userSupplyData.supply,
1362
1622
  liquidityExchangePrice: liqEp,
1363
- tokenExchangePrice: accruedTokenExchangePrice(lending.snapshot, liqEp, now)
1623
+ tokenExchangePrice: accruedTokenExchangePrice(lending.snapshot, liqEp, now, this.ctx.math)
1364
1624
  };
1365
1625
  }
1366
1626
  /** @deprecated Solana name; use `getFTokenInternalData`. */
1367
1627
  getJlTokenInternalData = (token) => this.getFTokenInternalData(token);
1628
+ /**
1629
+ * Generic lending state per fToken (identity, accrued + stored exchange prices, TVL, rates in
1630
+ * bps, rewards schedule) for `tokens` (underlying coin types), or every listed pool when
1631
+ * omitted. One batched `getObjects` for all pools + the on-chain Clock; metadata cached.
1632
+ */
1633
+ async getLendingStateData(tokens) {
1634
+ const list = (tokens ?? await this.listedLendingTokens()).map((t) => normalizeStructTag(t));
1635
+ const [{ pools, now }, metas] = await Promise.all([this.loadPools(list), Promise.all(list.map((t) => this.fTokenMeta(t)))]);
1636
+ return pools.map(({ lending, reserve }, i) => this.stateDataFrom(lending, reserve, metas[i].decimals, now));
1637
+ }
1638
+ /** Pure: `LendingStateData` from loaded records at `now`. */
1639
+ stateDataFrom(lending, reserve, decimals, now) {
1640
+ const m = this.ctx.math;
1641
+ const liq = calculateExchangePrice(reserve.snapshot, now, m);
1642
+ const tokenEp = accruedTokenExchangePrice(lending.snapshot, liq.supplyExchangePrice, now, m);
1643
+ const supplyRate = supplyRateToBps(supplyRateRaw(reserve.snapshot, liq.supplyExchangePrice, liq.borrowExchangePrice, m));
1644
+ const rewardsRate = rewardsRateToBps(currentRewardsRate(lending.snapshot, now, m));
1645
+ return {
1646
+ fTokenType: this.ctx.derive.fTokenType(lending.token),
1647
+ asset: lending.token,
1648
+ decimals,
1649
+ lendingStateId: lending.id,
1650
+ timestamp: now,
1651
+ tokenExchangePrice: tokenEp,
1652
+ liquidityExchangePrice: liq.supplyExchangePrice,
1653
+ storedTokenExchangePrice: lending.snapshot.tokenExchangePrice,
1654
+ storedLiquidityExchangePrice: lending.snapshot.liquidityExchangePrice,
1655
+ lastUpdateS: lending.snapshot.lastUpdateS,
1656
+ liquidityLastUpdateS: reserve.snapshot.lastUpdateTimestamp,
1657
+ totalShares: lending.snapshot.totalShares,
1658
+ totalAssets: toAssets(lending.snapshot.totalShares, tokenEp, false),
1659
+ supplyRate,
1660
+ rewardsRate,
1661
+ totalRate: supplyRate + rewardsRate,
1662
+ rewards: lending.snapshot.rewards ? { ...lending.snapshot.rewards } : null
1663
+ };
1664
+ }
1665
+ /**
1666
+ * Wallet holders of `Coin<FToken<T>>` for `token` (underlying coin type), via the GraphQL
1667
+ * indexer (`config.graphqlUrl`; needs `discovery: "indexer"`). Lists every Coin<FToken<T>>
1668
+ * object by type and keeps the ones with an ADDRESS owner (AddressOwner or
1669
+ * ConsensusAddressOwner), aggregated by owner, all pages at one checkpoint (consistent cursors).
1670
+ *
1671
+ * LIMITS (documented, by design): coins owned by other objects, shared/immutable coins and
1672
+ * wrapped `Balance<FToken<T>>` (inside other protocols' objects) are NOT attributed to a wallet;
1673
+ * nor are address balances (funds credited to an address without a coin object), since they are
1674
+ * not objects and cannot be enumerated by type. `totalSupply` is returned so a caller can
1675
+ * compare it with the sum of the wallets. With `verify` (default true) each wallet is cross-checked against
1676
+ * fullnode `getBalance` (coinBalance + addressBalance), `concurrency` at a time (default 8).
1677
+ */
1678
+ async getFTokenHolders(token, opts = {}) {
1679
+ if (!this.ctx.allows("indexer")) throw new NeedsDiscoveryError("fToken holder list", "indexer", "no on-chain holder registry exists (fToken coins are owned objects); only an indexer can list them by type");
1680
+ const url = this.ctx.config.graphqlUrl;
1681
+ if (!url) throw new Error("config.graphqlUrl not set (needed for the holder list)");
1682
+ const asset = normalizeStructTag(token);
1683
+ const fTokenType = this.ctx.derive.fTokenType(asset);
1684
+ const stateId = this.ctx.derive.lendingStateId(asset);
1685
+ const coinType = `0x2::coin::Coin<${fTokenType}>`;
1686
+ const NODES = `pageInfo { hasNextPage endCursor }
1687
+ nodes { owner { __typename ... on AddressOwner { address { address } } ... on ConsensusAddressOwner { address { address } } ... on ObjectOwner { address { address } } }
1688
+ asMoveObject { contents { json } } }`;
1689
+ const first = `query($t: String!, $s: SuiAddress!, $n: Int!) {
1690
+ checkpoint { sequenceNumber }
1691
+ state: object(address: $s) { asMoveObject { contents { json } } }
1692
+ objects(first: $n, filter: { type: $t }) { ${NODES} } }`;
1693
+ const next = `query($t: String!, $n: Int!, $a: String) { objects(first: $n, after: $a, filter: { type: $t }) { ${NODES} } }`;
1694
+ const n = opts.pageSize ?? 50;
1695
+ const byOwner = /* @__PURE__ */ new Map();
1696
+ let page = await graphqlQuery(url, first, {
1697
+ t: coinType,
1698
+ s: stateId,
1699
+ n
1700
+ });
1701
+ const checkpoint = BigInt(page.checkpoint.sequenceNumber);
1702
+ const supplyJson = page.state?.asMoveObject?.contents.json.supply;
1703
+ if (supplyJson === void 0) throw new Error(`no LendingState ${stateId} for ${asset}`);
1704
+ for (;;) {
1705
+ for (const node of page.objects.nodes) {
1706
+ const bal = BigInt(node.asMoveObject?.contents.json.balance ?? 0);
1707
+ const kind = node.owner?.__typename;
1708
+ if ((kind === "AddressOwner" || kind === "ConsensusAddressOwner") && node.owner?.address) {
1709
+ const k = normalizeSuiAddress(node.owner.address.address);
1710
+ const e = byOwner.get(k) ?? {
1711
+ balance: 0n,
1712
+ coins: 0
1713
+ };
1714
+ e.balance += bal;
1715
+ e.coins += 1;
1716
+ byOwner.set(k, e);
1717
+ }
1718
+ }
1719
+ if (!page.objects.pageInfo.hasNextPage) break;
1720
+ page = await graphqlQuery(url, next, {
1721
+ t: coinType,
1722
+ n,
1723
+ a: page.objects.pageInfo.endCursor
1724
+ });
1725
+ }
1726
+ const holders = [...byOwner.entries()].map(([owner, e]) => ({
1727
+ owner,
1728
+ balance: e.balance,
1729
+ coins: e.coins
1730
+ })).sort((a, b) => a.balance === b.balance ? 0 : a.balance > b.balance ? -1 : 1);
1731
+ if (opts.verify !== false) {
1732
+ const limit = Math.max(1, opts.concurrency ?? 8);
1733
+ let i = 0;
1734
+ const worker = async () => {
1735
+ while (i < holders.length) {
1736
+ const h = holders[i++];
1737
+ const { balance: b } = await this.ctx.client.core.getBalance({
1738
+ owner: h.owner,
1739
+ coinType: fTokenType
1740
+ });
1741
+ h.rpc = {
1742
+ balance: BigInt(b.balance),
1743
+ coinBalance: BigInt(b.coinBalance ?? b.balance),
1744
+ addressBalance: BigInt(b.addressBalance ?? 0)
1745
+ };
1746
+ h.agrees = h.rpc.coinBalance === h.balance && h.rpc.addressBalance === 0n;
1747
+ }
1748
+ };
1749
+ await Promise.all(Array.from({ length: Math.min(limit, holders.length) }, worker));
1750
+ }
1751
+ return {
1752
+ fTokenType,
1753
+ asset,
1754
+ checkpoint,
1755
+ holders,
1756
+ countedTotal: holders.reduce((a, h) => a + h.balance, 0n),
1757
+ totalSupply: BigInt(typeof supplyJson === "object" ? supplyJson.value : supplyJson)
1758
+ };
1759
+ }
1760
+ /** `[rewards writer cap id, current rewards rate in bps]`. */
1368
1761
  async getFTokenRewards(token) {
1369
- const { lending } = await this.loadOne(token);
1370
- return [lending.raw.rewards_writer ?? null, currentRewardsRate(lending.snapshot, this.ctx.now())];
1762
+ const { lending, now } = await this.loadOne(token);
1763
+ return [lending.raw.rewards_writer ?? null, rewardsRateToBps(currentRewardsRate(lending.snapshot, now, this.ctx.math))];
1371
1764
  }
1372
1765
  /** @deprecated Solana name; use `getFTokenRewards`. */
1373
1766
  getJlTokenRewards = (token) => this.getFTokenRewards(token);
@@ -1380,7 +1773,7 @@ var Lending = class {
1380
1773
  startTime: r.startS,
1381
1774
  endTime: r.endS,
1382
1775
  startTvl: r.startTvl,
1383
- maxRate: 50000000000000n,
1776
+ maxRate: rewardsRateToBps(MAX_REWARDS_RATE),
1384
1777
  rewardAmount: r.yearlyReward,
1385
1778
  nextStartTime: r.nextStartS,
1386
1779
  nextEndTime: r.nextEndS,
@@ -1405,16 +1798,30 @@ var Lending = class {
1405
1798
  return {
1406
1799
  fTokenShares: s,
1407
1800
  underlyingAssets: toAssets(s, ep, false),
1408
- underlyingBalance: BigInt(under.balance.balance),
1409
- allowance: 0n
1801
+ underlyingBalance: BigInt(under.balance.balance)
1410
1802
  };
1411
1803
  }
1412
1804
  async getUserPositions(user) {
1413
- const tokens = await this.getAllFTokens();
1414
- return Promise.all(tokens.map(async (t) => ({
1415
- fTokenDetails: await this.getFTokenDetails(t),
1416
- userPosition: await this.getUserPosition(t, user)
1417
- })));
1805
+ const tokens = await this.listedLendingTokens();
1806
+ const details = await this.detailsWithPrices(tokens);
1807
+ const balances = await Promise.all(tokens.flatMap((t) => [this.ctx.client.core.getBalance({
1808
+ owner: user,
1809
+ coinType: this.ctx.derive.fTokenType(t)
1810
+ }), this.ctx.client.core.getBalance({
1811
+ owner: user,
1812
+ coinType: t
1813
+ })]));
1814
+ return details.map(({ details: d, tokenEp }, i) => {
1815
+ const shares = BigInt(balances[2 * i].balance.balance);
1816
+ return {
1817
+ fTokenDetails: d,
1818
+ userPosition: {
1819
+ fTokenShares: shares,
1820
+ underlyingAssets: toAssets(shares, tokenEp, false),
1821
+ underlyingBalance: BigInt(balances[2 * i + 1].balance.balance)
1822
+ }
1823
+ };
1824
+ });
1418
1825
  }
1419
1826
  /** `lending::auth::is_admin(&Auth, addr)` via simulation. */
1420
1827
  async isLendingAdmin(addr) {
@@ -1549,11 +1956,15 @@ var Liquidity = class {
1549
1956
  constructor(ctx) {
1550
1957
  this.ctx = ctx;
1551
1958
  }
1552
- /** Reserves + their rate-data fields in 2 batched round trips. */
1553
- async loadReserves(tokens) {
1959
+ /** Object ids `loadReserves` reads: every reserve, then every reserve's rate-data field. */
1960
+ reserveObjectIds(tokens) {
1961
+ const ids = tokens.map((t) => this.ctx.derive.tokenReserveId(normalizeStructTag(t)));
1962
+ return [...ids, ...ids.map((id) => this.ctx.derive.rateDataId(id))];
1963
+ }
1964
+ /** Parse objects fetched for `reserveObjectIds(tokens)` (same order). */
1965
+ reservesFromObjects(tokens, objs) {
1554
1966
  const norm = tokens.map((t) => normalizeStructTag(t));
1555
1967
  const ids = norm.map((t) => this.ctx.derive.tokenReserveId(t));
1556
- const objs = await fetchObjects(this.ctx.client, [...ids, ...ids.map((id) => this.ctx.derive.rateDataId(id))]);
1557
1968
  return norm.map((token, i) => {
1558
1969
  const o = objs[i];
1559
1970
  if (!o) throw new Error(`no TokenReserve for ${token} (${ids[i]})`);
@@ -1567,6 +1978,10 @@ var Liquidity = class {
1567
1978
  };
1568
1979
  });
1569
1980
  }
1981
+ /** Reserves + their rate-data fields in one batched `getObjects`. */
1982
+ async loadReserves(tokens) {
1983
+ return this.reservesFromObjects(tokens, await fetchObjects(this.ctx.client, this.reserveObjectIds(tokens)));
1984
+ }
1570
1985
  async loadReserve(token) {
1571
1986
  return (await this.loadReserves([token]))[0];
1572
1987
  }
@@ -1678,9 +2093,9 @@ var Liquidity = class {
1678
2093
  maxUtilization: s.maxUtilization
1679
2094
  };
1680
2095
  }
1681
- /** Pure: accrue a reserve snapshot to `now` (defaults to the context clock). */
2096
+ /** Pure: accrue a reserve snapshot to `now` (unix seconds; use `await ctx.now()` for chain time). */
1682
2097
  calculateExchangePrice(snapshot, now) {
1683
- return calculateExchangePrice(snapshot, now ?? this.ctx.now());
2098
+ return calculateExchangePrice(snapshot, now, this.ctx.math);
1684
2099
  }
1685
2100
  async getRateConfig(token) {
1686
2101
  const { rateData: r } = await this.loadReserve(token);
@@ -1715,8 +2130,8 @@ var Liquidity = class {
1715
2130
  return { maxUtilization: (await this.loadReserve(token)).snapshot.maxUtilization };
1716
2131
  }
1717
2132
  async getRevenue(token) {
1718
- const { snapshot } = await this.loadReserve(token);
1719
- const ep = calculateExchangePrice(snapshot, this.ctx.now());
2133
+ const [{ snapshot }, now] = await Promise.all([this.loadReserve(token), this.ctx.now()]);
2134
+ const ep = calculateExchangePrice(snapshot, now, this.ctx.math);
1720
2135
  return calcRevenue(snapshot, ep.supplyExchangePrice, ep.borrowExchangePrice);
1721
2136
  }
1722
2137
  async getTokenRateData(token) {
@@ -1725,15 +2140,16 @@ var Liquidity = class {
1725
2140
  async getTokensRateData(tokens) {
1726
2141
  return (await this.loadReserves(tokens)).map((r) => r.rateData);
1727
2142
  }
1728
- overallFromRecord(r, now = this.ctx.now()) {
1729
- return processOverallTokenData(r.snapshot, r.rateData, now);
2143
+ overallFromRecord(r, now) {
2144
+ return processOverallTokenData(r.snapshot, r.rateData, now, this.ctx.math);
1730
2145
  }
1731
2146
  async getOverallTokenData(token) {
1732
- return this.overallFromRecord(await this.loadReserve(token));
2147
+ const [r, now] = await Promise.all([this.loadReserve(token), this.ctx.now()]);
2148
+ return this.overallFromRecord(r, now);
1733
2149
  }
1734
2150
  async getOverallTokensData(tokens) {
1735
- const now = this.ctx.now();
1736
- return (await this.loadReserves(tokens)).map((r) => this.overallFromRecord(r, now));
2151
+ const [rs, now] = await Promise.all([this.loadReserves(tokens), this.ctx.now()]);
2152
+ return rs.map((r) => this.overallFromRecord(r, now));
1737
2153
  }
1738
2154
  async getAllOverallTokensData() {
1739
2155
  return this.getOverallTokensData(await this.listedTokens());
@@ -1755,14 +2171,14 @@ var Liquidity = class {
1755
2171
  }]);
1756
2172
  return e.borrow ?? 0n;
1757
2173
  }
1758
- supplyDataFromRecords(reserve, acc, now = this.ctx.now()) {
2174
+ supplyDataFromRecords(reserve, acc, now) {
1759
2175
  const overallTokenData = this.overallFromRecord(reserve, now);
1760
2176
  return {
1761
2177
  userSupplyData: processUserSupplyData(acc, overallTokenData, reserve.snapshot.vaultBalance, now),
1762
2178
  overallTokenData
1763
2179
  };
1764
2180
  }
1765
- borrowDataFromRecords(reserve, acc, now = this.ctx.now()) {
2181
+ borrowDataFromRecords(reserve, acc, now) {
1766
2182
  const overallTokenData = this.overallFromRecord(reserve, now);
1767
2183
  return {
1768
2184
  userBorrowData: processUserBorrowData(acc, overallTokenData, reserve.snapshot.vaultBalance, now),
@@ -1770,28 +2186,27 @@ var Liquidity = class {
1770
2186
  };
1771
2187
  }
1772
2188
  async getUserSupplyData(user, token) {
1773
- const reserve = await this.loadReserve(token);
2189
+ const [reserve, now] = await Promise.all([this.loadReserve(token), this.ctx.now()]);
1774
2190
  const [e] = await this.loadUserEntries([{
1775
2191
  holder: user,
1776
2192
  reserve
1777
2193
  }]);
1778
- return this.supplyDataFromRecords(reserve, e.supply);
2194
+ return this.supplyDataFromRecords(reserve, e.supply, now);
1779
2195
  }
1780
2196
  async getUserBorrowData(user, token) {
1781
- const reserve = await this.loadReserve(token);
2197
+ const [reserve, now] = await Promise.all([this.loadReserve(token), this.ctx.now()]);
1782
2198
  const [e] = await this.loadUserEntries([{
1783
2199
  holder: user,
1784
2200
  reserve
1785
2201
  }]);
1786
- return this.borrowDataFromRecords(reserve, e.borrow);
2202
+ return this.borrowDataFromRecords(reserve, e.borrow, now);
1787
2203
  }
1788
2204
  async getUserMultipleSupplyData(user, tokens) {
1789
- const reserves = await this.loadReserves(tokens);
2205
+ const [reserves, now] = await Promise.all([this.loadReserves(tokens), this.ctx.now()]);
1790
2206
  const entries = await this.loadUserEntries(reserves.map((reserve) => ({
1791
2207
  holder: user,
1792
2208
  reserve
1793
2209
  })));
1794
- const now = this.ctx.now();
1795
2210
  const out = reserves.map((r, i) => this.supplyDataFromRecords(r, entries[i].supply, now));
1796
2211
  return {
1797
2212
  userSuppliesData: out.map((o) => o.userSupplyData),
@@ -1799,12 +2214,11 @@ var Liquidity = class {
1799
2214
  };
1800
2215
  }
1801
2216
  async getUserMultipleBorrowData(user, tokens) {
1802
- const reserves = await this.loadReserves(tokens);
2217
+ const [reserves, now] = await Promise.all([this.loadReserves(tokens), this.ctx.now()]);
1803
2218
  const entries = await this.loadUserEntries(reserves.map((reserve) => ({
1804
2219
  holder: user,
1805
2220
  reserve
1806
2221
  })));
1807
- const now = this.ctx.now();
1808
2222
  const out = reserves.map((r, i) => this.borrowDataFromRecords(r, entries[i].borrow, now));
1809
2223
  return {
1810
2224
  userBorrowingsData: out.map((o) => o.userBorrowData),
@@ -1922,13 +2336,11 @@ function pick(o, keys) {
1922
2336
  * returns `null` for a side whose view aborts, with the abort in `operateError` / `liquidateError`.
1923
2337
  * `getOracleRoutes` reads each route on its own (RedStone via the adapter's view, Pyth by decoding
1924
2338
  * the PriceInfoObject) so callers see which route set the price and whether the guard is live.
1925
- *
1926
- * admin (2026-09-18 preset only): `Oracle { name, rate }`, `view::quote(&Oracle)`.
1927
2339
  */
1928
2340
  /** Oracle rates are debt-per-collateral in the vault's internal 1e9 space, scaled by 1e15. */
1929
2341
  const ORACLE_RATE_PRECISION = 1000000000000000n;
1930
2342
  /** Mirrors `fluid_oracle::constants` of the deployed v2 package. */
1931
- const ORACLE_V2_LIMITS = {
2343
+ const ORACLE_LIMITS = {
1932
2344
  rateOutputDecimals: 15,
1933
2345
  maxAgeOperateS: 600n,
1934
2346
  maxAgeLiquidateS: 7200n,
@@ -1951,9 +2363,9 @@ function toSource(s) {
1951
2363
  }
1952
2364
  /** `rate::scale_and_invert`: raw provider price with `decimals` → 1e15 rate, optionally inverted. */
1953
2365
  function scaleAndInvert(raw, decimals, invert) {
1954
- const out = ORACLE_V2_LIMITS.rateOutputDecimals;
2366
+ const out = ORACLE_LIMITS.rateOutputDecimals;
1955
2367
  let rate = decimals > out ? raw / 10n ** BigInt(decimals - out) : raw * 10n ** BigInt(out - decimals);
1956
- if (rate > 0n && invert) rate = rate > ORACLE_V2_LIMITS.maxInvertibleRate ? 0n : ORACLE_RATE_PRECISION * ORACLE_RATE_PRECISION / rate;
2368
+ if (rate > 0n && invert) rate = rate > ORACLE_LIMITS.maxInvertibleRate ? 0n : ORACLE_RATE_PRECISION * ORACLE_RATE_PRECISION / rate;
1957
2369
  return rate;
1958
2370
  }
1959
2371
  function abortText(e) {
@@ -1968,26 +2380,15 @@ var Oracle = class {
1968
2380
  constructor(ctx) {
1969
2381
  this.ctx = ctx;
1970
2382
  }
1971
- get v2() {
1972
- const c = this.ctx.config.oracleV2;
1973
- if (this.ctx.config.oracleVariant !== "v2" || !c) throw new Error("config has no v2 oracle (oracleVariant/oracleV2)");
2383
+ get providers() {
2384
+ const c = this.ctx.config.oracle;
2385
+ if (!c) throw new Error("config has no oracle (config.oracle)");
1974
2386
  return c;
1975
2387
  }
1976
- /** Admin oracle object (2026-09-18 preset). */
1977
- async getOracle(oracleId) {
1978
- const id = normalizeSuiObjectId(oracleId);
1979
- const o = AdminOracle.parse((await fetchObject(this.ctx.client, id, "Oracle")).content);
1980
- return {
1981
- id,
1982
- name: o.name,
1983
- rate: BigInt(o.rate),
1984
- version: BigInt(o.version)
1985
- };
1986
- }
1987
2388
  /** v2 oracle object: nonce + main / guard routes. */
1988
- async getOracleV2(oracleId) {
2389
+ async getOracle(oracleId) {
1989
2390
  const id = normalizeSuiObjectId(oracleId);
1990
- const o = OracleV2.parse((await fetchObject(this.ctx.client, id, "Oracle")).content);
2391
+ const o = OracleObject.parse((await fetchObject(this.ctx.client, id, "Oracle")).content);
1991
2392
  return {
1992
2393
  id,
1993
2394
  nonce: o.nonce,
@@ -1997,18 +2398,18 @@ var Oracle = class {
1997
2398
  }
1998
2399
  /** Provider objects the v2 views take for this oracle. */
1999
2400
  providerArgs(o) {
2000
- const cfg = this.v2;
2401
+ const cfg = this.providers;
2001
2402
  const adapter = [o.main, o.guard].find((s) => s?.provider === "redstone")?.adapterId ?? cfg.redstoneAdapter;
2002
2403
  const pythFeed = [o.main, o.guard].find((s) => s?.provider === "pyth")?.feedId;
2003
2404
  const byFeed = Object.fromEntries(Object.entries(cfg.pythPriceInfoObjects).map(([k, v]) => [k.toLowerCase(), v]));
2004
2405
  const pythInfo = pythFeed ? byFeed[pythFeed.toLowerCase()] : Object.values(cfg.pythPriceInfoObjects)[0];
2005
- if (!pythInfo) throw new Error(`no Pyth PriceInfoObject configured for feed ${pythFeed ?? "(any)"} (config.oracleV2.pythPriceInfoObjects)`);
2406
+ if (!pythInfo) throw new Error(`no Pyth PriceInfoObject configured for feed ${pythFeed ?? "(any)"} (config.oracle.pythPriceInfoObjects)`);
2006
2407
  return {
2007
2408
  adapter: normalizeSuiObjectId(adapter),
2008
2409
  pythInfo: normalizeSuiObjectId(pythInfo)
2009
2410
  };
2010
2411
  }
2011
- async quoteV2(o, fn) {
2412
+ async quote(o, fn) {
2012
2413
  const pkg = this.ctx.config.packages.oracle.packageId;
2013
2414
  const { adapter, pythInfo } = this.providerArgs(o);
2014
2415
  const res = await simulateView(this.ctx.client, (tx) => {
@@ -2022,7 +2423,7 @@ var Oracle = class {
2022
2423
  ]
2023
2424
  });
2024
2425
  });
2025
- const q = PriceQuoteV2.parse(res[0][0]);
2426
+ const q = PriceQuote.parse(res[0][0]);
2026
2427
  const r = fn === "get_exchange_rate_operate" ? q.rate_operate : q.rate_liquidate;
2027
2428
  if (r == null) throw new Error(`${fn} returned no rate`);
2028
2429
  return BigInt(r);
@@ -2030,29 +2431,12 @@ var Oracle = class {
2030
2431
  /** Price exactly as vaults see it. v2: operate and liquidate simulated separately (one can abort alone). */
2031
2432
  async getOraclePrice(oracleId) {
2032
2433
  const id = normalizeSuiObjectId(oracleId);
2033
- if (this.ctx.config.oracleVariant === "admin") {
2034
- const pkg = this.ctx.config.packages.oracle.packageId;
2035
- const res = await simulateView(this.ctx.client, (tx) => {
2036
- tx.moveCall({
2037
- target: `${pkg}::view::quote`,
2038
- arguments: [tx.object(id)]
2039
- });
2040
- });
2041
- const q = PriceQuote.parse(res[0][0]);
2042
- return {
2043
- oracle: q.oracle_id,
2044
- operate: BigInt(q.rate_operate),
2045
- liquidate: BigInt(q.rate_liquidate),
2046
- source: "admin"
2047
- };
2048
- }
2049
- const o = await this.getOracleV2(id);
2050
- const [op, liq] = await Promise.allSettled([this.quoteV2(o, "get_exchange_rate_operate"), this.quoteV2(o, "get_exchange_rate_liquidate")]);
2434
+ const o = await this.getOracle(id);
2435
+ const [op, liq] = await Promise.allSettled([this.quote(o, "get_exchange_rate_operate"), this.quote(o, "get_exchange_rate_liquidate")]);
2051
2436
  return {
2052
2437
  oracle: id,
2053
2438
  operate: op.status === "fulfilled" ? op.value : null,
2054
2439
  liquidate: liq.status === "fulfilled" ? liq.value : null,
2055
- source: "v2",
2056
2440
  ...op.status === "rejected" ? { operateError: abortText(op.reason) } : {},
2057
2441
  ...liq.status === "rejected" ? { liquidateError: abortText(liq.reason) } : {}
2058
2442
  };
@@ -2063,8 +2447,8 @@ var Oracle = class {
2063
2447
  * Operate price = min over routes; liquidate = min over routes usable for liquidate.
2064
2448
  */
2065
2449
  async getOracleRoutes(oracleId) {
2066
- const o = await this.getOracleV2(oracleId);
2067
- const cfg = this.v2;
2450
+ const o = await this.getOracle(oracleId);
2451
+ const cfg = this.providers;
2068
2452
  const { pythInfo } = this.providerArgs(o);
2069
2453
  const redstone = [o.main, o.guard].filter((s) => s?.provider === "redstone");
2070
2454
  const res = await simulateView(this.ctx.client, (tx) => {
@@ -2089,7 +2473,7 @@ var Oracle = class {
2089
2473
  const pyth = o.main.provider === "pyth" || o.guard?.provider === "pyth" ? PythPriceInfoObject.parse((await fetchObject(this.ctx.client, pythInfo, "PriceInfoObject")).content) : null;
2090
2474
  let rsIdx = 0;
2091
2475
  const route = (s) => {
2092
- const L = ORACLE_V2_LIMITS;
2476
+ const L = ORACLE_LIMITS;
2093
2477
  if (s.provider === "redstone") {
2094
2478
  const k = 1 + 2 * rsIdx++;
2095
2479
  const value = BigInt(bcs.u256().parse(res[k][0]));
@@ -2632,7 +3016,6 @@ var Vault = class {
2632
3016
  liquidationPenalty: r.risk.liquidation_penalty,
2633
3017
  borrowFee: r.risk.borrow_fee,
2634
3018
  oracle: r.oracle,
2635
- rebalancer: "",
2636
3019
  liquidityProgram: r.liquidity_program,
2637
3020
  oracleProgram: this.ctx.config.packages.oracle.packageId,
2638
3021
  supplyToken: v.collateralType,
@@ -2729,10 +3112,10 @@ var Vault = class {
2729
3112
  return this.stateFromRecord(v, branch ?? null, counts ?? null);
2730
3113
  }
2731
3114
  /** `user::get_exchange_prices`: (liqSupply, liqBorrow, vaultSupply, vaultBorrow) accrued to `now`. */
2732
- exchangePricesFromRecords(v, colReserve, debtReserve, now = this.ctx.now()) {
3115
+ exchangePricesFromRecords(v, colReserve, debtReserve, now) {
2733
3116
  const r = v.raw;
2734
- const liqSupply = calculateExchangePrice(colReserve.snapshot, now).supplyExchangePrice;
2735
- const liqBorrow = calculateExchangePrice(debtReserve.snapshot, now).borrowExchangePrice;
3117
+ const liqSupply = calculateExchangePrice(colReserve.snapshot, now, this.ctx.math).supplyExchangePrice;
3118
+ const liqBorrow = calculateExchangePrice(debtReserve.snapshot, now, this.ctx.math).borrowExchangePrice;
2736
3119
  const last = BigInt(r.last_update_timestamp);
2737
3120
  const vp = loadVaultExchangePrices({
2738
3121
  vaultSupplyExPrice: BigInt(r.vault_supply_exchange_price),
@@ -2781,7 +3164,7 @@ var Vault = class {
2781
3164
  };
2782
3165
  }
2783
3166
  async entireDataFor(vaults, opts) {
2784
- const now = this.ctx.now();
3167
+ const nowP = this.ctx.now();
2785
3168
  const tokens = [...new Set(vaults.flatMap((v) => [v.collateralType, v.debtType]))];
2786
3169
  const reserves = new Map((await this.liquidity.loadReserves(tokens)).map((r) => [r.token, r]));
2787
3170
  const pairs = vaults.flatMap((v) => [{
@@ -2797,6 +3180,7 @@ var Vault = class {
2797
3180
  opts.withOracle === false ? Promise.resolve(vaults.map(() => null)) : Promise.all(vaults.map((v) => this.oracle.getOraclePrice(v.raw.oracle).catch(() => null))),
2798
3181
  this.countsOrNull(vaults, opts.positionCounts)
2799
3182
  ]);
3183
+ const now = await nowP;
2800
3184
  return vaults.map((v, i) => {
2801
3185
  const col = reserves.get(v.collateralType);
2802
3186
  const debt = reserves.get(v.debtType);
@@ -2868,7 +3252,6 @@ var Vault = class {
2868
3252
  liquidationPenalty: BigInt(cfg.liquidationPenalty),
2869
3253
  borrowFee: BigInt(cfg.borrowFee),
2870
3254
  oracle: cfg.oracle,
2871
- rebalancer: "",
2872
3255
  ...price?.operate != null ? { oraclePriceOperate: price.operate } : {},
2873
3256
  ...price?.liquidate != null ? { oraclePriceLiquidate: price.liquidate } : {},
2874
3257
  lastUpdateTimestamp: state.lastUpdateTimestamp
@@ -3133,34 +3516,59 @@ const DEFAULT_RPC_URL = "https://fullnode.mainnet.sui.io:443";
3133
3516
  * `client.liquidity`, `client.lending`, `client.vault` (+ `client.oracle`, Sui-only).
3134
3517
  *
3135
3518
  * @example
3136
- * const client = new Client(); // staging deployment, Sui mainnet gRPC
3137
- * const client = new Client("https://my-fullnode:443");
3519
+ * const client = new Client(undefined, { network: "mainnet" }); // production deployment, Sui mainnet gRPC
3520
+ * const client = new Client("https://my-fullnode:443", { network: customConfig });
3138
3521
  * const client = new Client(existingSuiGrpcClient, { network: "staging" });
3139
3522
  * const vault = await client.vault.getVaultEntireData(1);
3140
3523
  */
3141
- var Client = class {
3524
+ var Client = class Client {
3142
3525
  liquidity;
3143
3526
  lending;
3144
3527
  vault;
3145
3528
  oracle;
3146
3529
  context;
3147
- constructor(rpc, opts = {}) {
3148
- const config = resolveConfig(opts.network ?? DEFAULT_NETWORK);
3530
+ constructor(rpc, opts) {
3531
+ const config = resolveConfig(opts?.network);
3149
3532
  this.context = makeContext(createFailoverClient(rpc === void 0 ? config.rpcUrls ?? [DEFAULT_RPC_URL] : typeof rpc === "string" ? [rpc] : Array.isArray(rpc) ? rpc : [rpc], {
3150
3533
  network: config.network,
3151
- timeoutMs: opts.timeoutMs
3534
+ timeoutMs: opts.timeoutMs,
3535
+ ...opts.retry
3152
3536
  }), config, createDerive(config), opts.now, opts.discovery ?? "state");
3153
3537
  this.liquidity = new Liquidity(this.context);
3154
3538
  this.oracle = new Oracle(this.context);
3155
3539
  this.lending = new Lending(this.context, this.liquidity);
3156
3540
  this.vault = new Vault(this.context, this.liquidity, this.oracle);
3157
3541
  }
3542
+ /**
3543
+ * The same reader pinned to a past `checkpoint`: every object read comes from the GraphQL service
3544
+ * (`config.graphqlUrl`) at that checkpoint and the clock is the checkpoint's timestamp, so
3545
+ * `lending.getLendingStateData()`, rates, reserves and vault state are what the chain held then.
3546
+ * Move views / oracle quotes / balances are not available historically (`CheckpointReadError`).
3547
+ * Needs `discovery: "indexer"` (it is a GraphQL read).
3548
+ *
3549
+ * @example
3550
+ * const past = await client.atCheckpoint(330_000_000);
3551
+ * const state = await past.lending.getLendingStateData();
3552
+ */
3553
+ async atCheckpoint(checkpoint) {
3554
+ if (!this.context.allows("indexer")) throw new NeedsDiscoveryError("state at a past checkpoint", "indexer", "fullnodes serve only live object versions; historical object versions come from the GraphQL indexer");
3555
+ const url = this.context.config.graphqlUrl;
3556
+ if (!url) throw new Error("config.graphqlUrl not set (needed for checkpoint reads)");
3557
+ const cp = BigInt(checkpoint);
3558
+ const ts = await checkpointTimestampSeconds(url, cp);
3559
+ return new Client(checkpointClient(url, cp, this.context.client), {
3560
+ network: this.context.config,
3561
+ now: () => ts,
3562
+ discovery: "state",
3563
+ retry: { rounds: 1 }
3564
+ });
3565
+ }
3158
3566
  };
3159
3567
  /**
3160
3568
  * `$extend` form, same pattern as the write SDK's `fluid()`:
3161
3569
  * `const c = new SuiGrpcClient({...}).$extend(fluidRead()); c.fluidRead.vault...`
3162
3570
  */
3163
- function fluidRead(opts = {}) {
3571
+ function fluidRead(opts) {
3164
3572
  return {
3165
3573
  name: "fluidRead",
3166
3574
  register: (client) => new Client(client, opts)
@@ -3168,4 +3576,4 @@ function fluidRead(opts = {}) {
3168
3576
  }
3169
3577
 
3170
3578
  //#endregion
3171
- export { AllEndpointsFailedError, CONFIGS, Client, DEFAULT_NETWORK, DEFAULT_RPC_URL, EXCHANGE_PRICES_PRECISION, EXCHANGE_PRICE_SCALE_FACTOR, FOUR_DECIMALS, Lending, Liquidity, MAINNET, MAX_REWARDS_RATE, NeedsDiscoveryError, ORACLE_RATE_PRECISION, ORACLE_V2_LIMITS, ObjectNotFoundError, Oracle, POSITION_INDEX_FIX, PositionIndex, RATE_OUTPUT_DECIMALS, RETURN_PERCENT_PRECISION, SECONDS_PER_YEAR, STAGING, STAGING_LEGACY, Vault, ViewCallError, bcs_exports as bcsTypes, clientForEndpoint, configFromDeployment, createDerive, createFailoverClient, divCeil, fluidRead, lending_exports as lendingMath, liquidity_exports as liquidityMath, max, min, mulDiv, mulDivCeil, nowSeconds, resolveConfig, scaleAndInvert, simulateView, tick_exports as tickMath, vault_exports as vaultMath };
3579
+ export { AllEndpointsFailedError, CONFIGS, CheckpointReadError, Client, DEFAULT_RPC_URL, EXCHANGE_PRICES_PRECISION, EXCHANGE_PRICE_SCALE_FACTOR, FOUR_DECIMALS, GraphQLError, Lending, Liquidity, MAINNET, MAX_REWARDS_RATE, NeedsDiscoveryError, ORACLE_LIMITS, ORACLE_RATE_PRECISION, ObjectNotFoundError, Oracle, POSITION_INDEX_FIX, PROD, PositionIndex, RATE_OUTPUT_DECIMALS, RETURN_PERCENT_PRECISION, SECONDS_PER_YEAR, STAGING, SUPPLY_RATE_PRECISION, Vault, ViewCallError, backoffDelay, bcs_exports as bcsTypes, checkpointClient, checkpointTimestampSeconds, clientForEndpoint, configFromDeployment, createDerive, createFailoverClient, divCeil, fluidRead, graphqlQuery, latestCheckpoint, lending_exports as lendingMath, liquidity_exports as liquidityMath, max, min, mulDiv, mulDivCeil, nowSeconds, resolveConfig, rewardsRateToBps, scaleAndInvert, simulateView, supplyRateToBps, tick_exports as tickMath, vault_exports as vaultMath };