@exponent-labs/exponent-fetcher 0.1.7 → 0.9.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 (54) hide show
  1. package/build/constants.d.ts +26 -0
  2. package/build/constants.js +58 -0
  3. package/build/constants.js.map +1 -0
  4. package/build/exponentFetcher.d.ts +328 -31
  5. package/build/exponentFetcher.js +779 -168
  6. package/build/exponentFetcher.js.map +1 -1
  7. package/build/index.d.ts +11 -0
  8. package/build/index.js +11 -0
  9. package/build/index.js.map +1 -1
  10. package/build/utils/adrena.d.ts +13 -0
  11. package/build/utils/adrena.js +29 -0
  12. package/build/utils/adrena.js.map +1 -0
  13. package/build/utils/fragmetric.d.ts +17 -0
  14. package/build/utils/fragmetric.js +23 -0
  15. package/build/utils/fragmetric.js.map +1 -0
  16. package/build/utils/jito.d.ts +7 -0
  17. package/build/utils/jito.js +29 -0
  18. package/build/utils/jito.js.map +1 -0
  19. package/build/utils/jupiter.d.ts +30 -0
  20. package/build/utils/jupiter.js +63 -0
  21. package/build/utils/jupiter.js.map +1 -0
  22. package/build/utils/kamino.d.ts +5 -0
  23. package/build/utils/kamino.js +10 -0
  24. package/build/utils/kamino.js.map +1 -0
  25. package/build/utils/meteora.d.ts +19 -0
  26. package/build/utils/meteora.js +36 -1
  27. package/build/utils/meteora.js.map +1 -1
  28. package/build/utils/ore.d.ts +74 -0
  29. package/build/utils/ore.js +217 -0
  30. package/build/utils/ore.js.map +1 -0
  31. package/build/utils/perena.d.ts +12 -0
  32. package/build/utils/perena.js +27 -0
  33. package/build/utils/perena.js.map +1 -0
  34. package/build/utils/sanctum.d.ts +6 -0
  35. package/build/utils/sanctum.js +26 -0
  36. package/build/utils/sanctum.js.map +1 -0
  37. package/build/utils/solstice.d.ts +12 -0
  38. package/build/utils/solstice.js +45 -0
  39. package/build/utils/solstice.js.map +1 -0
  40. package/package.json +21 -18
  41. package/src/constants.ts +56 -0
  42. package/src/exponentFetcher.ts +1238 -222
  43. package/src/index.ts +11 -0
  44. package/src/utils/adrena.ts +44 -0
  45. package/src/utils/fragmetric.ts +34 -0
  46. package/src/utils/jito.ts +30 -0
  47. package/src/utils/jupiter.ts +98 -0
  48. package/src/utils/kamino.ts +6 -0
  49. package/src/utils/meteora.ts +73 -1
  50. package/src/utils/ore.ts +322 -0
  51. package/src/utils/perena.ts +28 -0
  52. package/src/utils/sanctum.ts +24 -0
  53. package/src/utils/solstice.ts +51 -0
  54. package/tsconfig.json +4 -1
@@ -1,14 +1,32 @@
1
- import { AnchorProvider, BN, Idl, Program, Wallet, web3 } from "@coral-xyz/anchor"
1
+ import { AnchorProvider, BN, DISCRIMINATOR_SIZE, Idl, Program, ProgramAccount, Wallet, web3 } from "@coral-xyz/anchor"
2
2
  import { BorshCoder } from "@coral-xyz/anchor"
3
3
  import { getStakePoolAccount } from "@solana/spl-stake-pool"
4
4
  import { getAccount, getMint } from "@solana/spl-token"
5
- import { MintLayout } from "@solana/spl-token"
5
+ import bs58 from "bs58"
6
6
  import Decimal from "decimal.js"
7
+
8
+ import {
9
+ CustodyState,
10
+ FeesStats,
11
+ PoolState,
12
+ calculateTotalFeesFromCustodies,
13
+ decodePoolAccount as decodeAdrenaPoolAccount,
14
+ decodeCustodyAccount,
15
+ decodePoolAndCustodyAccounts,
16
+ } from "@exponent-labs/adrena-idl"
17
+ import { decodeChainlinkPriceAccount } from "@exponent-labs/chainlink-idl"
18
+ import {
19
+ PROGRAM_ID as EXPONENT_CLMM_PROGRAM_ID,
20
+ ExponentClmm,
21
+ IDL as ExponentClmmIdl,
22
+ } from "@exponent-labs/exponent-clmm-idl"
7
23
  import { ExponentCore, IDL, PROGRAM_ID } from "@exponent-labs/exponent-idl"
24
+ import { IDL as EXPONENT_ORDERBOOK_IDL, ExponentOrderbook } from "@exponent-labs/exponent-orderbook-idl"
8
25
  import {
9
26
  AnchorizedPNum,
10
27
  AnchorizedPNumJson,
11
28
  CpiAccountIndexes,
29
+ ExponentCoreCpiIndexes,
12
30
  GenericSyMetaAccount,
13
31
  GenericSyMetaAccountRaw,
14
32
  InterfaceType,
@@ -16,6 +34,7 @@ import {
16
34
  JitoRestakingSyMetaAccountRaw,
17
35
  MarginfiSyMeta,
18
36
  MarginfiSyMetaRaw,
37
+ MarketCpiCoreIndexes,
19
38
  PerenaSyMetaAccount,
20
39
  PerenaSyMetaAccountRaw,
21
40
  SyEmissionRaw,
@@ -25,44 +44,48 @@ import {
25
44
  deserializeJitoRestakingSyMetaAccountRaw,
26
45
  deserializePerenaSyMetaAccountRaw,
27
46
  } from "@exponent-labs/exponent-types"
28
- import { IDL as FragmetricIdl } from "@exponent-labs/fragmetric-idl"
29
- import { IDL as JupiterLendIdl } from "@exponent-labs/jupiter-lend-idl"
30
- import { IDL as KaminoVaultIdl } from "@exponent-labs/kamino-vault-idl"
47
+ import {
48
+ MAX_OFFERS,
49
+ MAX_PRICE_NODES,
50
+ MAX_USER_ESCROWS,
51
+ OfferNode,
52
+ PriceTreeNode,
53
+ UserEscrowNode,
54
+ } from "@exponent-labs/exponent-types"
31
55
  import { GenericStandard } from "@exponent-labs/generic-sy-idl"
32
56
  import { PROGRAM_ID as GENERIC_STANDARD_PROGRAM_ID, IDL as GenericStandardIdl } from "@exponent-labs/generic-sy-idl"
33
57
  import { PROGRAM_ID as JITO_RESTAKING_SY_PROGRAM_ID } from "@exponent-labs/jito-restaking-sy-idl"
34
58
  import { JitoRestakingStandard, IDL as JitoRestakingSyIdl } from "@exponent-labs/jito-restaking-sy-idl"
59
+ // Decode Jupiter Perps pool accounts via helper using Anchor 0.29.0
60
+ import { decodePoolAccount as decodeJupiterPerpsPoolAccount } from "@exponent-labs/jupiter-perps-idl"
35
61
  import { Obligation, Reserve } from "@exponent-labs/kamino-reserve-deserializer"
36
62
  import { PROGRAM_ID as KAMINO_LEND_PROGRAM_ID } from "@exponent-labs/kamino-reserve-deserializer"
37
63
  import { PROGRAM_ID as KAMINO_STANDARD_PROGRAM_ID } from "@exponent-labs/kamino-sy-idl"
38
64
  import { KaminoLendStandard } from "@exponent-labs/kamino-sy-idl"
39
65
  import { IDL as KaminoSyIdl } from "@exponent-labs/kamino-sy-idl"
66
+ import { IDL as KaminoVaultIdl } from "@exponent-labs/kamino-vault-idl"
40
67
  import {
41
68
  PROGRAM_ID as MARGINFI_SY_PROGRAM_ID,
42
69
  MarginfiStandard,
43
70
  IDL as MarginfiSyIdl,
44
71
  } from "@exponent-labs/marginfi-sy-idl"
45
- import { decodePoolAccount, decodeVaultAccount } from "@exponent-labs/meteora-idl"
72
+ import { fetchPoolAccount, fetchVaultAccount } from "@exponent-labs/meteora-idl"
46
73
  import { PROGRAM_ID as PERENA_STANDARD_PROGRAM_ID } from "@exponent-labs/perena-sy-idl"
47
74
  import { IDL as PerenaSyIdl } from "@exponent-labs/perena-sy-idl"
48
75
  import { PerenaStandard } from "@exponent-labs/perena-sy-idl"
49
76
  import { PreciseNumber } from "@exponent-labs/precise-number"
50
- import { decodePoolStateAccount } from "@exponent-labs/sanctum-idl"
51
- import {
52
- decodePoolAccount as decodeAdrenaPoolAccount,
53
- decodeCustodyAccount,
54
- CustodyState,
55
- FeesStats,
56
- PoolState,
57
- decodePoolAndCustodyAccounts,
58
- calculateTotalFeesFromCustodies,
59
- } from "@exponent-labs/adrena-idl"
60
77
  import { decodeYieldPoolAndVestingScheduleAccounts } from "@exponent-labs/solstice-idl"
61
78
 
79
+ import { IgnoredClmmEntityError, isMarketIgnored, isTicksIgnored } from "./constants"
80
+ import { calculateAdrenaIndex } from "./utils/adrena"
81
+ import { calculateFragmetricIndex, calculateFragmetricSupportedTokenIndex } from "./utils/fragmetric"
82
+ import { decodeJitoVaultData } from "./utils/jito"
83
+ import { calculateJupiterLendIndex, calculateJupiterPerpsIndex } from "./utils/jupiter"
62
84
  import { computeD, getAmountByShare } from "./utils/meteora"
63
- import { Adrena } from "@exponent-labs/adrena-idl/src/adrena"
64
- // Decode Jupiter Perps pool accounts via helper using Anchor 0.29.0
65
- import { decodePoolAccount as decodeJupiterPerpsPoolAccount } from "@exponent-labs/jupiter-perps-idl"
85
+ import { calculateOreExchangeRate } from "./utils/ore"
86
+ import { getPerenaLpMint, getPerenaStablePoolData } from "./utils/perena"
87
+ import { calculateSanctumIndex } from "./utils/sanctum"
88
+ import { calculateSolsticeRedemptionRate } from "./utils/solstice"
66
89
 
67
90
  export function serializeAnchorizedPNumFromJson(pnum: AnchorizedPNum): AnchorizedPNumJson {
68
91
  const serializedArray = pnum[0].map((bn) => bn.toString())
@@ -111,6 +134,8 @@ export class ExponentFetcher {
111
134
  public jitoRestakingSyProgram: Program<JitoRestakingStandard>
112
135
  public perenaSyProgram: Program<PerenaStandard>
113
136
  public genericStandardProgram: Program<GenericStandard>
137
+ public exponentClmmProgram: Program<ExponentClmm>
138
+ public orderbookProgram: Program<ExponentOrderbook>
114
139
  public connection: web3.Connection
115
140
  public coreProgramId: web3.PublicKey
116
141
  public marginfiSyProgramId: web3.PublicKey
@@ -156,6 +181,8 @@ export class ExponentFetcher {
156
181
  )
157
182
  this.perenaSyProgram = new Program<PerenaStandard>(PerenaSyIdl as PerenaStandard, provider)
158
183
  this.genericStandardProgram = new Program<GenericStandard>(GenericStandardIdl, provider)
184
+ this.exponentClmmProgram = new Program<ExponentClmm>(ExponentClmmIdl as ExponentClmm, provider)
185
+ this.orderbookProgram = new Program<ExponentOrderbook>(EXPONENT_ORDERBOOK_IDL as ExponentOrderbook, provider)
159
186
  }
160
187
 
161
188
  async fetchVault(address: web3.PublicKey) {
@@ -180,6 +207,30 @@ export class ExponentFetcher {
180
207
  }
181
208
  }
182
209
 
210
+ async fetchOrderbook(address: web3.PublicKey): Promise<Orderbook> {
211
+ try {
212
+ const o = (await this.connection.getAccountInfo(address)).data
213
+
214
+ return deserializeOrderbook(o)
215
+ } catch (e) {
216
+ console.error(`Error fetching orderbook ${address.toBase58()}`)
217
+ console.error(e)
218
+ throw e
219
+ }
220
+ }
221
+
222
+ async fetchOrderbookCpiAccounts(address: web3.PublicKey): Promise<OrderbookCpiAccountsRaw> {
223
+ try {
224
+ const orderbookCpiAccounts: OrderbookCpiAccountsRaw =
225
+ await this.orderbookProgram.account.cpiAccountsOrderbook.fetch(address)
226
+ return orderbookCpiAccounts
227
+ } catch (e) {
228
+ console.error(`Error fetching orderbook ${address.toBase58()}`)
229
+ console.error(e)
230
+ throw e
231
+ }
232
+ }
233
+
183
234
  async fetchMarginfiSyMeta(address: web3.PublicKey): Promise<MarginfiSyMeta> {
184
235
  const x: MarginfiSyMetaRaw = await this.marginfiSyProgram.account.syMeta.fetch(address)
185
236
  return deserializeMarginfiSyMeta(x)
@@ -263,11 +314,62 @@ export class ExponentFetcher {
263
314
  return deserializeLpPosition(x)
264
315
  }
265
316
 
317
+ async fetchLpPositionCLMM(address: web3.PublicKey) {
318
+ try {
319
+ const raw = await this.exponentClmmProgram.account.lpPosition.fetch(address)
320
+
321
+ const v: LpPositionCLMMRaw = {
322
+ owner: raw.owner,
323
+ market: raw.market,
324
+ feeInsideLastPt: raw.feeInsideLastPt,
325
+ feeInsideLastSy: raw.feeInsideLastSy,
326
+ lpBalance: raw.lpBalance,
327
+ tokensOwedSy: raw.tokensOwedSy,
328
+ tokensOwedPt: raw.tokensOwedPt,
329
+ lowerTickIdx: raw.lowerTickIdx,
330
+ upperTickIdx: raw.upperTickIdx,
331
+ farms: {
332
+ trackers: raw.farms.trackers.map((t: any) => ({
333
+ staged: t.staged,
334
+ lastSeenIndex: t.lastSeenIndex,
335
+ })),
336
+ },
337
+ shareTrackers: {
338
+ trackers: raw.shareTrackers.trackers.map((tracker: any) => ({
339
+ tickIdx: tracker.tickIdx,
340
+ rightTickIdx: tracker.rightTickIdx,
341
+ splitEpoch: tracker.splitEpoch,
342
+ lpShare: tracker.lpShare,
343
+ emissions: {
344
+ trackers: tracker.emissions.trackers.map((e: any) => ({
345
+ staged: e.staged,
346
+ lastSeenIndex: e.lastSeenIndex,
347
+ })),
348
+ },
349
+ })),
350
+ },
351
+ }
352
+
353
+ return deserializeLpPositionCLMM(v)
354
+ } catch (e) {
355
+ console.error(`Error fetching clmm lp position ${address.toBase58()}`)
356
+ console.error(e)
357
+ throw e
358
+ }
359
+ }
360
+
266
361
  async fetchYtPosition(address: web3.PublicKey): Promise<YtPosition> {
267
362
  const x: YtPositionRaw = await this.program.account.yieldTokenPosition.fetch(address)
268
363
  return deserializeYtPosition(x)
269
364
  }
270
365
 
366
+ /** Batch fetch multiple YT positions in a single RPC call */
367
+ async fetchYtPositions(addresses: web3.PublicKey[]): Promise<(YtPosition | null)[]> {
368
+ if (addresses.length === 0) return []
369
+ const results = await this.program.account.yieldTokenPosition.fetchMultiple(addresses)
370
+ return results.map((x) => (x ? deserializeYtPosition(x as YtPositionRaw) : null))
371
+ }
372
+
271
373
  async fetchJitoRestakingSyMeta(address: web3.PublicKey): Promise<JitoRestakingSyMetaAccount> {
272
374
  const x: JitoRestakingSyMetaAccountRaw = await this.jitoRestakingSyProgram.account.syMeta.fetch(address)
273
375
 
@@ -284,6 +386,365 @@ export class ExponentFetcher {
284
386
 
285
387
  return deserializeGenericSyMetaAccountRaw(x)
286
388
  }
389
+
390
+ async fetchAllMarketThree(): Promise<MarketThree[]> {
391
+ //TODO Replace with the following code when all damaged markets are removed onchain
392
+ // const marketsProgramAccounts: ProgramAccount<MarketThreeRaw>[] =
393
+ // await this.exponentClmmProgram.account.marketThree.all()
394
+ // return marketsProgramAccounts.map(({ account }) => deserializeMarketThree(account))
395
+
396
+ const MARKET_THREE_DISCRIMINATOR = Buffer.from([242, 240, 26, 15, 148, 186, 185, 205])
397
+ const marketsProgramAccounts = await this.connection.getProgramAccounts(this.exponentClmmProgram.programId, {
398
+ filters: [
399
+ {
400
+ memcmp: {
401
+ offset: 0,
402
+ bytes: bs58.encode(MARKET_THREE_DISCRIMINATOR),
403
+ },
404
+ },
405
+ ],
406
+ })
407
+
408
+ return marketsProgramAccounts
409
+ .filter(({ pubkey }) => !isMarketIgnored(pubkey.toBase58()))
410
+ .map(({ account, pubkey }) => {
411
+ try {
412
+ return this.exponentClmmProgram.coder.accounts.decode("marketThree", account.data)
413
+ } catch (error) {
414
+ return null
415
+ }
416
+ })
417
+ .filter((m) => !!m)
418
+ }
419
+
420
+ async fetchMarketThree(address: web3.PublicKey): Promise<MarketThree> {
421
+ const addressStr = address.toBase58()
422
+ if (isMarketIgnored(addressStr)) {
423
+ throw new IgnoredClmmEntityError("market", addressStr)
424
+ }
425
+
426
+ try {
427
+ const m: MarketThreeRaw = await this.exponentClmmProgram.account.marketThree.fetch(address)
428
+ return deserializeMarketThree(m)
429
+ } catch (e) {
430
+ console.error(`Error fetching market ${addressStr}`)
431
+ console.error(e)
432
+ throw e
433
+ }
434
+ }
435
+
436
+ async fetchAllMarketThreeTicks(): Promise<Ticks[]> {
437
+ const TICKS_DISCRIMINATOR = Buffer.from([122, 104, 41, 141, 214, 36, 222, 37])
438
+
439
+ const ticksAccounts = await this.connection.getProgramAccounts(this.exponentClmmProgram.programId, {
440
+ filters: [
441
+ {
442
+ memcmp: {
443
+ offset: 0,
444
+ bytes: bs58.encode(TICKS_DISCRIMINATOR),
445
+ },
446
+ },
447
+ ],
448
+ })
449
+
450
+ return ticksAccounts
451
+ .filter(({ pubkey }) => !isTicksIgnored(pubkey.toBase58()))
452
+ .map(({ account }) => deserializeMarketThreeTicks(account.data))
453
+ }
454
+
455
+ async fetchMarketThreeTicks(address: web3.PublicKey): Promise<Ticks> {
456
+ const addressStr = address.toBase58()
457
+ if (isTicksIgnored(addressStr)) {
458
+ throw new IgnoredClmmEntityError("ticks", addressStr)
459
+ }
460
+
461
+ try {
462
+ const m = (await this.connection.getAccountInfo(address)).data
463
+ return deserializeMarketThreeTicks(m)
464
+ } catch (e) {
465
+ console.error(`Error fetching ticks ${addressStr}`)
466
+ console.error(e)
467
+ throw e
468
+ }
469
+ }
470
+ }
471
+
472
+ /**
473
+ * Deserializes a CLMM Ticks account from raw buffer data.
474
+ *
475
+ * Layout matches Rust struct `Ticks` with `RedBlackTree<u32, Tick, 1000>`:
476
+ * - Discriminator: 8 bytes
477
+ * - RedBlackTree header: root(4) + padding(12) + size(8) + bump(4) + freeIdx(4) = 32 bytes
478
+ * - 1000 RBTree nodes, each: nodeHeader(16) + key(4) + padding(4) + Tick = variable bytes
479
+ * - Ticks footer: market(32) + feeGrowthPt(16) + feeGrowthSy(16) + prefixSum(8) + spotPrice(8) + currentTick(4) + padding(12) = 96 bytes
480
+ */
481
+ export function deserializeMarketThreeTicks(data: Buffer): Ticks {
482
+ const MAX_TICK_NODES = 1000
483
+ const PERSONAL_TICK_YIELD_TRACKER_SIZE = 2
484
+ let offset = 8 // Skip discriminator
485
+
486
+ // ─── Helper functions ─────────────────────────────────────────────────────
487
+ const readU64 = (): bigint => {
488
+ const val = data.readBigUInt64LE(offset)
489
+ offset += 8
490
+ return val
491
+ }
492
+
493
+ const readU128 = (): bigint => {
494
+ const lo = data.readBigUInt64LE(offset)
495
+ const hi = data.readBigUInt64LE(offset + 8)
496
+ offset += 16
497
+ return (hi << 64n) + lo
498
+ }
499
+
500
+ const readI128 = (): bigint => {
501
+ const lo = data.readBigUInt64LE(offset)
502
+ const hi = data.readBigInt64LE(offset + 8) // High part is signed
503
+ offset += 16
504
+ return (hi << 64n) + lo
505
+ }
506
+
507
+ const readF64 = (): number => {
508
+ const val = data.readDoubleLE(offset)
509
+ offset += 8
510
+ return val
511
+ }
512
+
513
+ const readU32 = (): number => {
514
+ const val = data.readUInt32LE(offset)
515
+ offset += 4
516
+ return val
517
+ }
518
+
519
+ const readPubkey = (): web3.PublicKey => {
520
+ const pk = new web3.PublicKey(data.slice(offset, offset + 32))
521
+ offset += 32
522
+ return pk
523
+ }
524
+
525
+ /** Reads a PreciseNumber (Number type in Rust) as float - 32 bytes (4 x u64) */
526
+ const readPreciseNumberAsFloat = (): number => {
527
+ const nums: BN[] = []
528
+ for (let i = 0; i < 4; i++) {
529
+ nums.push(new BN(data.slice(offset + i * 8, offset + (i + 1) * 8), undefined, "le"))
530
+ }
531
+ offset += 32
532
+ return parseFloat(PreciseNumber.fromRaw(nums).valueString)
533
+ }
534
+
535
+ /** Reads a PreciseNumber (Number type in Rust) as raw bigint - 32 bytes (4 x u64 = 256 bits) */
536
+ const readPreciseNumberAsBigint = (): bigint => {
537
+ let val = 0n
538
+ for (let i = 0; i < 4; i++) {
539
+ const chunk = data.readBigUInt64LE(offset + i * 8)
540
+ val += chunk << BigInt(i * 64)
541
+ }
542
+ offset += 32
543
+ return val
544
+ }
545
+
546
+ const skip = (bytes: number): void => {
547
+ offset += bytes
548
+ }
549
+
550
+ // ─── Parse RedBlackTree header ────────────────────────────────────────────
551
+ skip(4) // root: u32
552
+ skip(12) // padding to align NodeAllocator
553
+ skip(8) // size: u64
554
+ skip(4) // bump_index: u32
555
+ skip(4) // free_list_head: u32
556
+
557
+ // ─── Parse tick nodes ─────────────────────────────────────────────────────
558
+ const ticks: Tick[] = []
559
+
560
+ for (let i = 0; i < MAX_TICK_NODES; i++) {
561
+ // RBNode header: left(4) + right(4) + parent(4) + color(4) = 16 bytes
562
+ skip(16)
563
+
564
+ // Key: u32 + padding to 8 bytes
565
+ const apyBasePoints = readU32()
566
+ skip(4) // padding
567
+
568
+ // Tick value (416 bytes total)
569
+ const feeGrowthOutsidePt = readU128() // 16 bytes
570
+ const feeGrowthOutsideSy = readU128() // 16 bytes
571
+ const liquidityNet = readI128() // 16 bytes
572
+ const liquidityGross = readU64() // 8 bytes
573
+ const spotPrice = readF64() // 8 bytes
574
+ const principalPt = readU64() // 8 bytes
575
+ const principalSy = readU64() // 8 bytes
576
+ const principalShareSupply = readPreciseNumberAsBigint() // 32 bytes - kept as bigint for arithmetic
577
+
578
+ // FarmYieldTrackers: 2 x FarmYieldTracker(32 bytes) = 64 bytes
579
+ const farms: { lastSeenIndex: number }[] = []
580
+ for (let j = 0; j < PERSONAL_TICK_YIELD_TRACKER_SIZE; j++) {
581
+ farms.push({ lastSeenIndex: readPreciseNumberAsFloat() })
582
+ }
583
+
584
+ // EmissionYieldTrackers: 2 x EmissionYieldTracker(64 bytes) = 128 bytes
585
+ const emissions: { lastSeenIndex: number; lastPositionIndex: number }[] = []
586
+ for (let j = 0; j < PERSONAL_TICK_YIELD_TRACKER_SIZE; j++) {
587
+ emissions.push({
588
+ lastSeenIndex: readPreciseNumberAsFloat(),
589
+ lastPositionIndex: readPreciseNumberAsFloat(),
590
+ })
591
+ }
592
+
593
+ const lastSplitEpoch = readU64() // 8 bytes
594
+ const frozenLiquidity = readU64() // 8 bytes
595
+
596
+ ticks.push({
597
+ apyBasePoints,
598
+ liquidityNet,
599
+ feeGrowthOutsidePt,
600
+ feeGrowthOutsideSy,
601
+ liquidityGross,
602
+ impliedRate: spotPrice, // Legacy field name kept for compatibility
603
+ principalPt,
604
+ principalSy,
605
+ principalShareSupply,
606
+ farms,
607
+ emissions,
608
+ lastSplitEpoch,
609
+ frozenLiquidity,
610
+ })
611
+ }
612
+
613
+ // ─── Parse Ticks footer ───────────────────────────────────────────────────
614
+ const market = readPubkey() // 32 bytes
615
+ const feeGrowthIndexGlobalPt = readU128() // 16 bytes
616
+ const feeGrowthIndexGlobalSy = readU128() // 16 bytes
617
+ const currentPrefixSum = readU64() // 8 bytes
618
+ const currentSpotPrice = readF64() // 8 bytes
619
+ const currentTick = readU32() // 4 bytes
620
+ skip(12) // padding
621
+
622
+ return {
623
+ ticksTree: ticks,
624
+ market,
625
+ feeGrowthIndexGlobalPt,
626
+ feeGrowthIndexGlobalSy,
627
+ currentPrefixSum,
628
+ currentSpotPrice,
629
+ currentTick,
630
+ }
631
+ }
632
+
633
+ /** Decoded account may use snake_case (from JSON IDL); normalize to camelCase for app use. */
634
+ function normalizeCpiContext(a: { altIndex?: number; alt_index?: number; isSigner?: boolean; is_signer?: boolean; isWritable?: boolean; is_writable?: boolean }): { altIndex: number; isSigner: boolean; isWritable: boolean } {
635
+ return {
636
+ altIndex: a.altIndex ?? (a as { alt_index?: number }).alt_index ?? 0,
637
+ isSigner: a.isSigner ?? (a as { is_signer?: boolean }).is_signer ?? false,
638
+ isWritable: a.isWritable ?? (a as { is_writable?: boolean }).is_writable ?? false,
639
+ }
640
+ }
641
+
642
+ function normalizeCpiAccountIndexes(
643
+ raw: {
644
+ getSyState?: unknown[]
645
+ get_sy_state?: unknown[]
646
+ withdrawSy?: unknown[]
647
+ withdraw_sy?: unknown[]
648
+ depositSy?: unknown[]
649
+ deposit_sy?: unknown[]
650
+ claimEmission?: unknown[][]
651
+ claim_emission?: unknown[][]
652
+ getPositionState?: unknown[]
653
+ get_position_state?: unknown[]
654
+ }
655
+ ): CpiAccountIndexes {
656
+ const arr = (key: string, snake: string) => {
657
+ const a = (raw as Record<string, unknown>)[key] ?? (raw as Record<string, unknown>)[snake]
658
+ return Array.isArray(a) ? a.map((x) => normalizeCpiContext(x as Record<string, unknown>)) : []
659
+ }
660
+ const arr2 = (key: string, snake: string) => {
661
+ const a = (raw as Record<string, unknown>)[key] ?? (raw as Record<string, unknown>)[snake]
662
+ return Array.isArray(a) ? a.map((inner) => (Array.isArray(inner) ? inner.map((x) => normalizeCpiContext(x as Record<string, unknown>)) : [])) : []
663
+ }
664
+ return {
665
+ getSyState: arr("getSyState", "get_sy_state"),
666
+ withdrawSy: arr("withdrawSy", "withdraw_sy"),
667
+ depositSy: arr("depositSy", "deposit_sy"),
668
+ claimEmission: arr2("claimEmission", "claim_emission"),
669
+ getPositionState: arr("getPositionState", "get_position_state"),
670
+ }
671
+ }
672
+
673
+ function normalizeMarketCpiCoreIndexes(
674
+ raw: {
675
+ stripSy?: unknown[]
676
+ strip_sy?: unknown[]
677
+ mergeSy?: unknown[]
678
+ merge_sy?: unknown[]
679
+ }
680
+ ): MarketCpiCoreIndexes {
681
+ const arr = (key: string, snake: string) => {
682
+ const a = (raw as Record<string, unknown>)[key] ?? (raw as Record<string, unknown>)[snake]
683
+ return Array.isArray(a) ? a.map((x) => normalizeCpiContext(x as Record<string, unknown>)) : []
684
+ }
685
+ return {
686
+ stripSy: arr("stripSy", "strip_sy"),
687
+ mergeSy: arr("mergeSy", "merge_sy"),
688
+ }
689
+ }
690
+
691
+ export function deserializeMarketThree(m: MarketThreeRaw): MarketThree {
692
+ const rawCpiSy = m.cpiSyAccounts ?? (m as unknown as { cpi_sy_accounts?: unknown }).cpi_sy_accounts
693
+ const rawCpiCore = m.cpiCoreAccounts ?? (m as unknown as { cpi_core_accounts?: unknown }).cpi_core_accounts
694
+ const cpiSyAccounts =
695
+ rawCpiSy != null && typeof rawCpiSy === "object"
696
+ ? normalizeCpiAccountIndexes(rawCpiSy as Parameters<typeof normalizeCpiAccountIndexes>[0])
697
+ : (m.cpiSyAccounts ?? { getSyState: [], withdrawSy: [], depositSy: [], claimEmission: [], getPositionState: [] })
698
+ const cpiCoreAccounts =
699
+ rawCpiCore != null && typeof rawCpiCore === "object"
700
+ ? normalizeMarketCpiCoreIndexes(rawCpiCore as Parameters<typeof normalizeMarketCpiCoreIndexes>[0])
701
+ : (m.cpiCoreAccounts ?? { stripSy: [], mergeSy: [] })
702
+
703
+ return {
704
+ addressLookupTable: m.addressLookupTable,
705
+ mintSy: m.mintSy,
706
+ mintPt: m.mintPt,
707
+ vault: m.vault,
708
+ tokenSyEscrow: m.tokenSyEscrow,
709
+ tokenPtEscrow: m.tokenPtEscrow,
710
+ tokenFeeTreasurySy: m.tokenFeeTreasurySy,
711
+ tokenFeeTreasuryPt: m.tokenFeeTreasuryPt,
712
+ selfAddress: m.selfAddress,
713
+ syProgram: m.syProgram,
714
+ statusFlags: m.statusFlags,
715
+ cpiSyAccounts,
716
+ isCurrentFlashSwap: m.isCurrentFlashSwap,
717
+ lpFarm: m.lpFarm,
718
+ mintYt: m.mintYt,
719
+ tokenYtEscrow: m.tokenYtEscrow,
720
+ emissions: {
721
+ trackers: m.emissions.trackers.map((t) => ({
722
+ tokenEscrow: t.tokenEscrow,
723
+ lpShareIndex: deserializeAnchorizedPNum(t.lpShareIndex),
724
+ lastSeenStaged: Number(t.lastSeenStaged),
725
+ })),
726
+ },
727
+ liquidityNetBalanceLimits: m.liquidityNetBalanceLimits,
728
+ admin: m.admin,
729
+ ticks: m.ticks,
730
+ configurationOptions: {
731
+ lnFeeRateRoot: m.configurationOptions.lnFeeRateRoot,
732
+ treasuryFeeBps: m.configurationOptions.treasuryFeeBps,
733
+ minLpTickAmount: BigInt(m.configurationOptions.minLpTickAmount.toString()),
734
+ epsilonClamp: m.configurationOptions.epsilonClamp,
735
+ maxLpSupply: BigInt(m.configurationOptions.maxLpSupply.toString()),
736
+ tickSpace: m.configurationOptions.tickSpace,
737
+ },
738
+ financials: {
739
+ expirationTs: BigInt(m.financials.expirationTs),
740
+ ptBalance: BigInt(m.financials.ptBalance.toString()),
741
+ syBalance: BigInt(m.financials.syBalance.toString()),
742
+ liquidityBalance: BigInt(m.financials.liquidityBalance.toString()),
743
+ },
744
+ cpiCoreAccounts,
745
+ exponentCoreProgram: m.exponentCoreProgram,
746
+ seedId: m.seedId,
747
+ }
287
748
  }
288
749
 
289
750
  function deserializeMarketTwo(m: MarketTwoRaw): MarketTwo {
@@ -324,6 +785,34 @@ function deserializeMarketTwo(m: MarketTwoRaw): MarketTwo {
324
785
  }
325
786
  }
326
787
 
788
+ function deserializeLpPositionCLMM(x: LpPositionCLMMRaw): LpPositionCLMM {
789
+ return {
790
+ owner: x.owner,
791
+ market: x.market,
792
+ feeInsideLastPt: BigInt(x.feeInsideLastPt.toString()),
793
+ feeInsideLastSy: BigInt(x.feeInsideLastSy.toString()),
794
+ lpBalance: BigInt(x.lpBalance.toString()),
795
+ tokensOwedSy: BigInt(x.tokensOwedSy.toString()),
796
+ tokensOwedPt: BigInt(x.tokensOwedPt.toString()),
797
+ lowerTickIdx: x.lowerTickIdx,
798
+ upperTickIdx: x.upperTickIdx,
799
+ farms: x.farms.trackers.map((t) => ({
800
+ staged: BigInt(t.staged.toString()),
801
+ lastSeenIndex: parseFloat(PreciseNumber.fromRaw(t.lastSeenIndex[0]).valueString),
802
+ })),
803
+ shareTrackers: x.shareTrackers.trackers.map((tracker) => ({
804
+ tickIdx: tracker.tickIdx,
805
+ rightTickIdx: tracker.rightTickIdx,
806
+ splitEpoch: BigInt(tracker.splitEpoch.toString()),
807
+ lpShare: anchorizedPNumToRawBigint(tracker.lpShare),
808
+ emissions: tracker.emissions.trackers.map((e) => ({
809
+ staged: BigInt(e.staged.toString()),
810
+ lastSeenIndex: parseFloat(PreciseNumber.fromRaw(e.lastSeenIndex[0]).valueString),
811
+ })),
812
+ })),
813
+ }
814
+ }
815
+
327
816
  function deserializeVault(x: VaultRaw): Vault {
328
817
  return {
329
818
  syProgram: x.syProgram,
@@ -355,6 +844,263 @@ function deserializeVault(x: VaultRaw): Vault {
355
844
  }
356
845
  }
357
846
 
847
+ function deserializeOrderbook(data: Buffer): Orderbook {
848
+ let offset = 0
849
+
850
+ // 1) Skip Anchor discriminator
851
+ offset += DISCRIMINATOR_SIZE
852
+
853
+ const readPubkey = (): web3.PublicKey => {
854
+ const pk = new web3.PublicKey(data.slice(offset, offset + 32))
855
+ offset += 32
856
+ return pk
857
+ }
858
+
859
+ // ConfigurationOptions struct
860
+ const thresholdAmount = data.readBigUInt64LE(offset)
861
+ offset += 8
862
+ const lnMakerFeeRate = data.readDoubleLE(offset)
863
+ offset += 8
864
+ const lnTakerFeeRate = data.readDoubleLE(offset)
865
+ offset += 8
866
+ const priceDecimals = data.readUint8(offset)
867
+ offset += 1
868
+ // Skip ConfigurationOptions padding: _placeholder_one[15] + _placeholder_two[32] + _placeholder_three[32] + _placeholder_four[32] + _reserved[1024] = 1135 bytes
869
+ offset += 1135
870
+
871
+ // Pubkeys
872
+ const vault = readPubkey()
873
+ const yieldPosition = readPubkey()
874
+ const addressLookupTable = readPubkey()
875
+ const exponentCoreProgram = readPubkey()
876
+ const syProgram = readPubkey()
877
+ const tokenEscrowSy = readPubkey()
878
+ const tokenEscrowYt = readPubkey()
879
+ const tokenEscrowPt = readPubkey()
880
+ const cpiAccountOrderbook = readPubkey()
881
+ const admin = readPubkey()
882
+
883
+ // last_sy_exchange_rate (Number type = 32 bytes, PreciseNumber with 12 decimals)
884
+ const lastSyExchangeRateRaw = (() => {
885
+ let val = 0n
886
+ for (let i = 0; i < 4; i++) {
887
+ val += data.readBigUInt64LE(offset + i * 8) << BigInt(i * 64)
888
+ }
889
+ return val
890
+ })()
891
+ offset += 32
892
+
893
+ // OrderbookFinancials struct
894
+ // Skip last_seen_sy_index (Number type = 32 bytes)
895
+ offset += 32
896
+ const ytBalance = data.readBigUInt64LE(offset)
897
+ offset += 8
898
+ const syBalance = data.readBigUInt64LE(offset)
899
+ offset += 8
900
+ const ptBalance = data.readBigUInt64LE(offset)
901
+ offset += 8
902
+ const ytFeeBalance = data.readBigUInt64LE(offset)
903
+ offset += 8
904
+ const syFeeBalance = data.readBigUInt64LE(offset)
905
+ offset += 8
906
+ const ptFeeBalance = data.readBigUInt64LE(offset)
907
+ offset += 8
908
+ const stagedSyBalance = data.readBigUInt64LE(offset)
909
+ offset += 8
910
+ const expirationTs = data.readUInt32LE(offset)
911
+ offset += 4
912
+ // Skip financials _padding: [u8; 4]
913
+ offset += 4
914
+
915
+ const configurationOptions: ConfigurationOptions = {
916
+ priceDecimals,
917
+ thresholdAmount,
918
+ lnMakerFeeRate,
919
+ lnTakerFeeRate,
920
+ }
921
+ const financials: OrderbookFinancials = {
922
+ expirationTs,
923
+ syBalance: syBalance,
924
+ ytBalance: ytBalance,
925
+ ptBalance: ptBalance,
926
+ ytFeeBalance,
927
+ syFeeBalance,
928
+ ptFeeBalance,
929
+ stagedSy: stagedSyBalance,
930
+ }
931
+ // console.log("financials", financials)
932
+ // ─── Parse RedBlackTree slab ───────────────────────────────────────────────
933
+ // RedBlackTree struct: root: u32, _padding: [u32; 3], allocator: NodeAllocator<...>
934
+ // Total before allocator = 4 + 12 = 16 bytes
935
+
936
+ const root = data.readUInt32LE(offset)
937
+ offset += 4
938
+ const padding = 12 // _padding: [u32; 3] in RedBlackTree struct
939
+ offset += padding
940
+
941
+ // NodeAllocator<T=RBNode<u32,PriceNode>, N=MAX_PRICE_NODES, R=3>
942
+ // header: size:u64, bump_index:u32, free_list_head:u32
943
+
944
+ const priceTreeSize = Number(data.readBigUInt64LE(offset))
945
+ offset += 8
946
+
947
+ const _priceTreeBump = data.readUInt32LE(offset)
948
+ offset += 4
949
+
950
+ const _priceTreeFreeIdx = data.readUInt32LE(offset)
951
+ offset += 4
952
+
953
+ // each RBNode entry = registers[3] + key:u32 + first_offer:u32
954
+ const prices: PriceTreeNode[] = []
955
+ for (let i = 0; i < MAX_PRICE_NODES; i++) {
956
+ const left = data.readUInt32LE(offset)
957
+ offset += 4
958
+ const right = data.readUInt32LE(offset)
959
+ offset += 4
960
+ const parent = data.readUInt32LE(offset)
961
+ offset += 4
962
+ offset += 4 // skip color
963
+ const key = data.readUInt32LE(offset)
964
+ offset += 4
965
+
966
+ const firstOfferSellYt = data.readUInt32LE(offset)
967
+ offset += 4
968
+ const firstOfferBuyYt = data.readUInt32LE(offset)
969
+ offset += 4
970
+ const lastOfferSellYt = data.readUInt32LE(offset)
971
+ offset += 4
972
+ const lastOfferBuyYt = data.readUInt32LE(offset)
973
+ offset += 4
974
+ if (key === 0) continue
975
+ prices.push({ key, firstOfferSellYt, firstOfferBuyYt, lastOfferSellYt, lastOfferBuyYt, parent, left, right })
976
+ }
977
+
978
+ // ─── Parse Offers slab ────────────────────────────────────────────────────
979
+ // NodeAllocator<Offer, MAX_OFFERS, 4>
980
+ // header: size:u64, bump_index:u32, free_list_head:u32
981
+
982
+ const offersSize = Number(data.readBigUInt64LE(offset))
983
+ offset += 8
984
+ const offersBumpIndex = data.readUInt32LE(offset)
985
+ offset += 4
986
+ const offersFreeListHead = data.readUInt32LE(offset)
987
+ offset += 4
988
+
989
+ const offers: OfferNode[] = []
990
+ for (let i = 0; i < MAX_OFFERS; i++) {
991
+ const register = data.readUInt32LE(offset)
992
+ offset += 4
993
+ const nextOfferPointer = data.readUInt32LE(offset)
994
+ offset += 4
995
+ const userVaultPointer = data.readUInt32LE(offset)
996
+ offset += 4
997
+ const pricePointer = data.readUInt32LE(offset)
998
+ offset += 4
999
+ const amount = data.readBigUInt64LE(offset)
1000
+ offset += 8
1001
+ const expiryAt = data.readUInt32LE(offset)
1002
+ offset += 4
1003
+ const createdAt = data.readUInt32LE(offset)
1004
+ offset += 4
1005
+ const virtualOffer = data.readUInt8(offset) !== 0
1006
+ offset += 1
1007
+ const orderTypeFlag = data.readUInt8(offset)
1008
+ offset += 1
1009
+ const fillOrKill = data.readUInt8(offset) !== 0
1010
+ offset += 1
1011
+ offset += 5 // reserved padding
1012
+ if (userVaultPointer === 0) continue
1013
+ offers.push({
1014
+ offerIndex: i + 1,
1015
+ nextOfferPointer,
1016
+ amount,
1017
+ userVaultPointer,
1018
+ expiryAt,
1019
+ createdAt,
1020
+ virtualOffer,
1021
+ orderTypeFlag,
1022
+ fillOrKill,
1023
+ pricePointer,
1024
+ })
1025
+ }
1026
+
1027
+ // ─── Parse UserEscrow slab ────────────────────────────────────────────────
1028
+ // NodeAllocator<UserEscrow, MAX_USER_ESCROWS, 2>
1029
+ // header: size:u64, bump_index:u32, free_list_head:u32
1030
+ const escSize = Number(data.readBigUInt64LE(offset))
1031
+ offset += 8
1032
+ const _escBump = data.readUInt32LE(offset)
1033
+ offset += 4
1034
+ const _escFreeIdx = data.readUInt32LE(offset)
1035
+ offset += 4
1036
+
1037
+ // each Node = [ no registers ] + UserEscrow.value
1038
+ const userEscrows: UserEscrowNode[] = []
1039
+ for (let i = 0; i < MAX_USER_ESCROWS; i++) {
1040
+ const register = data.readUInt32LE(offset)
1041
+ offset += 4
1042
+ const register2 = data.readUInt32LE(offset)
1043
+ offset += 4
1044
+ const user = new web3.PublicKey(data.slice(offset, offset + 32))
1045
+ offset += 32
1046
+ const yieldIndexRaw: AnchorizedPNum = [[new BN(0), new BN(0), new BN(0), new BN(0)]]
1047
+ for (let word = 0; word < 4; word++) {
1048
+ yieldIndexRaw[0][word] = new BN(data.subarray(offset + word * 8, offset + (word + 1) * 8), "le")
1049
+ }
1050
+ const yieldIndex = deserializeAnchorizedPNum(yieldIndexRaw)
1051
+ offset += 32
1052
+ const ptAmount = data.readBigUInt64LE(offset)
1053
+ offset += 8
1054
+ const syAmount = data.readBigUInt64LE(offset)
1055
+ offset += 8
1056
+ const ytAmount = data.readBigUInt64LE(offset)
1057
+ offset += 8
1058
+ const stakedYtAmount = data.readBigInt64LE(offset)
1059
+ offset += 8
1060
+ const staged = data.readBigInt64LE(offset)
1061
+ offset += 8
1062
+ offset += 8 // reserved
1063
+ // if (user.toBase58() == "11111111111111111111111111111111") continue
1064
+ userEscrows.push({ user, yieldIndex, ptAmount, syAmount, ytAmount, stakedYtAmount, staged })
1065
+ }
1066
+
1067
+ // ─── Finally, seed_id + signer_bump + reserved ─────────────────────────────
1068
+ // seed_id: [u8; 4]
1069
+ const seedId = [
1070
+ data.readUInt8(offset),
1071
+ data.readUInt8(offset + 1),
1072
+ data.readUInt8(offset + 2),
1073
+ data.readUInt8(offset + 3),
1074
+ ]
1075
+ offset += 4
1076
+ // signer_bump: [u8; 1]
1077
+ const signerBump = data.readUInt8(offset)
1078
+ offset += 1
1079
+ // _reserved: [u8; 3] - skip
1080
+ offset += 3
1081
+
1082
+ return {
1083
+ vault,
1084
+ yieldPosition,
1085
+ addressLookupTable,
1086
+ exponentCoreProgram,
1087
+ syProgram,
1088
+ admin,
1089
+ tokenEscrowSy,
1090
+ tokenEscrowYt,
1091
+ tokenEscrowPt,
1092
+ cpiAccountOrderbook,
1093
+ lastSyExchangeRate: lastSyExchangeRateRaw,
1094
+ financials,
1095
+ prices,
1096
+ configurationOptions,
1097
+ offers,
1098
+ userEscrows,
1099
+ offersBumpIndex,
1100
+ offersFreeListHead,
1101
+ }
1102
+ }
1103
+
358
1104
  function deserializeMarginfiSyMeta(x: MarginfiSyMetaRaw): MarginfiSyMeta {
359
1105
  return {
360
1106
  ...x,
@@ -386,7 +1132,7 @@ function deserializeSyEmissionRaw(x: {
386
1132
  }
387
1133
  }
388
1134
 
389
- function deserializeLpPosition(x: LpPositionRaw): LpPosition {
1135
+ export function deserializeLpPosition(x: LpPositionRaw): LpPosition {
390
1136
  return {
391
1137
  owner: x.owner,
392
1138
  market: x.market,
@@ -402,7 +1148,7 @@ function deserializeLpPosition(x: LpPositionRaw): LpPosition {
402
1148
  }
403
1149
  }
404
1150
 
405
- function deserializeYtPosition(x: YtPositionRaw): YtPosition {
1151
+ export function deserializeYtPosition(x: YtPositionRaw): YtPosition {
406
1152
  return {
407
1153
  owner: x.owner,
408
1154
  vault: x.vault,
@@ -419,16 +1165,30 @@ function deserializeYieldTokenTracker(x: YieldTokenTrackerRaw): YieldTokenTracke
419
1165
  }
420
1166
  }
421
1167
 
422
- export async function fetchKaminoReserve(address: web3.PublicKey, connection: web3.Connection) {
1168
+ export interface KaminoReserveSummary {
1169
+ lendingMarket: web3.PublicKey
1170
+ baseMint: web3.PublicKey
1171
+ assetShareValue: Decimal
1172
+ scopePriceFeed?: web3.PublicKey
1173
+ }
1174
+
1175
+ export async function fetchKaminoReserve(
1176
+ address: web3.PublicKey,
1177
+ connection: web3.Connection,
1178
+ ): Promise<KaminoReserveSummary> {
423
1179
  const reserve = await Reserve.fetch(connection, address)
424
1180
  if (!reserve) {
425
1181
  throw new Error("Reserve not found")
426
1182
  }
427
1183
 
1184
+ const rawScopePriceFeed = reserve.config.tokenInfo.scopeConfiguration.priceFeed
1185
+ const scopePriceFeed = rawScopePriceFeed.equals(web3.PublicKey.default) ? undefined : rawScopePriceFeed
1186
+
428
1187
  return {
429
1188
  lendingMarket: reserve.lendingMarket,
430
1189
  baseMint: reserve.liquidity.mintPubkey,
431
1190
  assetShareValue: reserve.getCollateralExchangeRate(),
1191
+ scopePriceFeed,
432
1192
  }
433
1193
  }
434
1194
 
@@ -492,6 +1252,160 @@ export function deserializeEmission(emission: VaultEmissionJson): VaultEmission
492
1252
  }
493
1253
  }
494
1254
 
1255
+ export interface MarketThree {
1256
+ admin: web3.PublicKey
1257
+ addressLookupTable: web3.PublicKey
1258
+ mintPt: web3.PublicKey
1259
+ mintSy: web3.PublicKey
1260
+ mintYt: web3.PublicKey
1261
+ vault: web3.PublicKey
1262
+ tokenPtEscrow: web3.PublicKey
1263
+ tokenSyEscrow: web3.PublicKey
1264
+ tokenYtEscrow: web3.PublicKey
1265
+ tokenFeeTreasurySy: web3.PublicKey
1266
+ tokenFeeTreasuryPt: web3.PublicKey
1267
+ syProgram: web3.PublicKey
1268
+ exponentCoreProgram: web3.PublicKey
1269
+ selfAddress: web3.PublicKey
1270
+ ticks: web3.PublicKey
1271
+ statusFlags: number
1272
+ configurationOptions: MarketConfigurationOptions
1273
+ financials: MarketThreeFinancials
1274
+ cpiSyAccounts: CpiAccountIndexes
1275
+ cpiCoreAccounts: MarketCpiCoreIndexes
1276
+ isCurrentFlashSwap: boolean
1277
+ lpFarm: LpFarm
1278
+ emissions: {
1279
+ trackers: {
1280
+ tokenEscrow: web3.PublicKey
1281
+ lpShareIndex: number
1282
+ lastSeenStaged: number
1283
+ }[]
1284
+ }
1285
+ liquidityNetBalanceLimits: LiquidityNetBalanceLimits
1286
+ seedId: number[]
1287
+ }
1288
+
1289
+ export interface Ticks {
1290
+ /** Current tick (left boundary of the active interval) */
1291
+ currentTick: number
1292
+ /** Array of ticks (simplified from RBTree for TypeScript) */
1293
+ ticksTree: Tick[]
1294
+ /** Market address this ticks account belongs to */
1295
+ market: web3.PublicKey
1296
+ /** Fee growth index global for PT */
1297
+ feeGrowthIndexGlobalPt: bigint
1298
+ /** Fee growth index global for SY */
1299
+ feeGrowthIndexGlobalSy: bigint
1300
+ /** Current prefix sum - the active liquidity at the current tick */
1301
+ currentPrefixSum: bigint
1302
+ /** Current spot price (ln implied rate) */
1303
+ currentSpotPrice: number
1304
+ }
1305
+
1306
+ export interface Tick {
1307
+ liquidityNet: bigint
1308
+ /** Total liquidity referencing this tick as a boundary */
1309
+ liquidityGross: bigint
1310
+ /** Fee growth index outside this tick for PT */
1311
+ feeGrowthOutsidePt: bigint
1312
+ /** Fee growth index outside this tick for SY */
1313
+ feeGrowthOutsideSy: bigint
1314
+ /** Principal PT accrued in the interval starting at this tick */
1315
+ impliedRate: number
1316
+ principalPt: bigint
1317
+ /** Principal SY accrued in the interval starting at this tick */
1318
+ principalSy: bigint
1319
+ apyBasePoints: number
1320
+ principalShareSupply: bigint
1321
+ /** Farm yield trackers (2 trackers) */
1322
+ farms: { lastSeenIndex: number }[]
1323
+ /** Emission yield trackers (2 trackers) */
1324
+ emissions: { lastSeenIndex: number; lastPositionIndex: number }[]
1325
+ /** Last split epoch for this tick */
1326
+ lastSplitEpoch: bigint
1327
+ /** Frozen liquidity that cannot be withdrawn */
1328
+ frozenLiquidity: bigint
1329
+ }
1330
+
1331
+ export interface MarketThreeRaw {
1332
+ admin: web3.PublicKey
1333
+ addressLookupTable: web3.PublicKey
1334
+ mintPt: web3.PublicKey
1335
+ mintSy: web3.PublicKey
1336
+ mintYt: web3.PublicKey
1337
+ vault: web3.PublicKey
1338
+ tokenPtEscrow: web3.PublicKey
1339
+ tokenSyEscrow: web3.PublicKey
1340
+ tokenYtEscrow: web3.PublicKey
1341
+ tokenFeeTreasurySy: web3.PublicKey
1342
+ tokenFeeTreasuryPt: web3.PublicKey
1343
+ syProgram: web3.PublicKey
1344
+ exponentCoreProgram: web3.PublicKey
1345
+ selfAddress: web3.PublicKey
1346
+ ticks: web3.PublicKey
1347
+ statusFlags: number
1348
+ configurationOptions: MarketConfigurationOptionsRaw
1349
+ financials: MarketThreeFinancialsRaw
1350
+ cpiSyAccounts: CpiAccountIndexes
1351
+ cpiCoreAccounts: MarketCpiCoreIndexes
1352
+ isCurrentFlashSwap: boolean
1353
+ lpFarm: LpFarm
1354
+ emissions: MarketEmissions
1355
+ liquidityNetBalanceLimits: LiquidityNetBalanceLimits
1356
+ seedId: number[]
1357
+ }
1358
+
1359
+ export interface MarketConfigurationOptions {
1360
+ /** ln of fee rate root */
1361
+ lnFeeRateRoot: number
1362
+ /** Treasury fee in basis points */
1363
+ treasuryFeeBps: number
1364
+ /** Minimum LP amount per tick */
1365
+ minLpTickAmount: bigint
1366
+ /** Epsilon clamp for numerical stability */
1367
+ epsilonClamp: number
1368
+ /** Maximum LP supply */
1369
+ maxLpSupply: bigint
1370
+ /** Tick space */
1371
+ tickSpace: number
1372
+ // priceDecimals: number ??
1373
+ }
1374
+
1375
+ export interface MarketThreeFinancials {
1376
+ expirationTs: bigint
1377
+ ptBalance: bigint
1378
+ syBalance: bigint
1379
+ liquidityBalance: bigint
1380
+ }
1381
+
1382
+ export interface MarketConfigurationOptionsRaw {
1383
+ lnFeeRateRoot: number
1384
+ treasuryFeeBps: number
1385
+ minLpTickAmount: BN
1386
+ epsilonClamp: number
1387
+ maxLpSupply: BN
1388
+ tickSpace: number
1389
+ }
1390
+
1391
+ export interface OrderbookConfigurationOptionsRaw {
1392
+ thresholdAmount: BN
1393
+ lnMakerFeeRate: number
1394
+ lnTakerFeeRate: number
1395
+ priceDecimals: number
1396
+ }
1397
+
1398
+ export interface MarketThreeFinancialsRaw {
1399
+ /** Expiration timestamp */
1400
+ expirationTs: number
1401
+ /** PT balance in the market */
1402
+ ptBalance: BN
1403
+ /** SY balance in the market */
1404
+ syBalance: BN
1405
+ /** Total liquidity balance */
1406
+ liquidityBalance: BN
1407
+ }
1408
+
495
1409
  export interface MarketTwo {
496
1410
  ptBalance: bigint
497
1411
  syBalance: bigint
@@ -557,6 +1471,48 @@ export interface Vault {
557
1471
  maxPySupply: bigint
558
1472
  }
559
1473
 
1474
+ interface OrderbookFinancials {
1475
+ expirationTs: number
1476
+ ytBalance: bigint
1477
+ syBalance: bigint
1478
+ ptBalance: bigint
1479
+ ytFeeBalance: bigint
1480
+ syFeeBalance: bigint
1481
+ ptFeeBalance: bigint
1482
+ stagedSy: bigint
1483
+ }
1484
+
1485
+ export interface ConfigurationOptions {
1486
+ priceDecimals: number
1487
+ thresholdAmount: bigint
1488
+ lnMakerFeeRate: number
1489
+ lnTakerFeeRate: number
1490
+ }
1491
+
1492
+ export interface Orderbook {
1493
+ vault: web3.PublicKey
1494
+ yieldPosition: web3.PublicKey
1495
+ addressLookupTable: web3.PublicKey
1496
+ exponentCoreProgram: web3.PublicKey
1497
+ syProgram: web3.PublicKey
1498
+ tokenEscrowSy: web3.PublicKey
1499
+ tokenEscrowYt: web3.PublicKey
1500
+ tokenEscrowPt: web3.PublicKey
1501
+ cpiAccountOrderbook: web3.PublicKey
1502
+ admin: web3.PublicKey
1503
+ /** Raw 256-bit PreciseNumber (12 decimals) for last SY exchange rate */
1504
+ lastSyExchangeRate: bigint
1505
+ configurationOptions: ConfigurationOptions
1506
+ financials: OrderbookFinancials
1507
+ prices: PriceTreeNode[]
1508
+ offers: OfferNode[]
1509
+ userEscrows: UserEscrowNode[]
1510
+ /** Next offer index that will be allocated (from NodeAllocator free list) */
1511
+ offersFreeListHead: number
1512
+ /** Bump index boundary for offers allocator */
1513
+ offersBumpIndex: number
1514
+ }
1515
+
560
1516
  export interface KaminoSyMeta {
561
1517
  kaminoReserve: web3.PublicKey
562
1518
  kaminoObligation: web3.PublicKey
@@ -604,7 +1560,7 @@ export interface YieldTokenTracker {
604
1560
  lastSeenIndex: number
605
1561
  }
606
1562
 
607
- interface LpPositionRaw {
1563
+ export interface LpPositionRaw {
608
1564
  owner: web3.PublicKey
609
1565
  market: web3.PublicKey
610
1566
  lpBalance: BN
@@ -612,6 +1568,48 @@ interface LpPositionRaw {
612
1568
  farms: { trackers: { staged: BN; lastSeenIndex: AnchorizedPNum }[] }
613
1569
  }
614
1570
 
1571
+ interface LpPositionCLMMRaw {
1572
+ owner: web3.PublicKey
1573
+ market: web3.PublicKey
1574
+ feeInsideLastPt: BN
1575
+ feeInsideLastSy: BN
1576
+ lpBalance: BN
1577
+ tokensOwedSy: BN
1578
+ tokensOwedPt: BN
1579
+ lowerTickIdx: number
1580
+ upperTickIdx: number
1581
+ farms: { trackers: { staged: BN; lastSeenIndex: AnchorizedPNum }[] }
1582
+ shareTrackers: {
1583
+ trackers: {
1584
+ tickIdx: number
1585
+ rightTickIdx: number
1586
+ splitEpoch: BN
1587
+ lpShare: AnchorizedPNum
1588
+ emissions: { trackers: { staged: BN; lastSeenIndex: AnchorizedPNum }[] }
1589
+ }[]
1590
+ }
1591
+ }
1592
+
1593
+ export interface LpPositionCLMM {
1594
+ owner: web3.PublicKey
1595
+ market: web3.PublicKey
1596
+ feeInsideLastPt: bigint
1597
+ feeInsideLastSy: bigint
1598
+ lpBalance: bigint
1599
+ tokensOwedSy: bigint
1600
+ tokensOwedPt: bigint
1601
+ lowerTickIdx: number
1602
+ upperTickIdx: number
1603
+ farms: { staged: bigint; lastSeenIndex: number }[]
1604
+ shareTrackers: {
1605
+ tickIdx: number
1606
+ rightTickIdx: number
1607
+ splitEpoch: bigint
1608
+ lpShare: bigint
1609
+ emissions: { staged: bigint; lastSeenIndex: number }[]
1610
+ }[]
1611
+ }
1612
+
615
1613
  export interface LpFarm {
616
1614
  lastSeenTimestamp: number
617
1615
  farmEmissions: FarmEmissionRaw[]
@@ -636,7 +1634,7 @@ export interface MarketEmissions {
636
1634
  trackers: MarketEmission[]
637
1635
  }
638
1636
 
639
- interface MarketEmission {
1637
+ export interface MarketEmission {
640
1638
  tokenEscrow: web3.PublicKey
641
1639
  lpShareIndex: AnchorizedPNum
642
1640
  lastSeenStaged: BN
@@ -719,7 +1717,7 @@ interface KaminoSyMetaRaw {
719
1717
  emissions: SyEmissionRaw[]
720
1718
  }
721
1719
 
722
- interface YtPositionRaw {
1720
+ export interface YtPositionRaw {
723
1721
  owner: web3.PublicKey
724
1722
  vault: web3.PublicKey
725
1723
  ytBalance: BN
@@ -732,10 +1730,76 @@ interface YieldTokenTrackerRaw {
732
1730
  lastSeenIndex: AnchorizedPNum
733
1731
  }
734
1732
 
1733
+ export interface OrderbookCpiAccountsRaw {
1734
+ syCpiAccounts: CpiAccountIndexes
1735
+ exponentCoreCpiAccounts: ExponentCoreCpiIndexes
1736
+ }
1737
+
1738
+ interface OrderbookRaw {
1739
+ vault: web3.PublicKey
1740
+ yieldPosition: web3.PublicKey
1741
+ addressLookupTable: web3.PublicKey
1742
+ exponentCoreProgram: web3.PublicKey
1743
+ cpiAccountOrderbook: web3.PublicKey
1744
+ tokenEscrowSy: web3.PublicKey
1745
+ tokenEscrowYt: web3.PublicKey
1746
+ tokenEscrowPt: web3.PublicKey
1747
+ nodeCount: number
1748
+ financials: OrderbookFinancialsRaw
1749
+ prices: PriceTreeNodeRaw[]
1750
+ offers: OfferNodeRaw[]
1751
+ userEscrows: UserEscrowNodeRaw[]
1752
+ }
1753
+
1754
+ interface OrderbookFinancialsRaw {
1755
+ expirationTs: number
1756
+ ytBalance: BN
1757
+ syBalance: BN
1758
+ ptBalance: BN
1759
+ }
1760
+
1761
+ export interface PriceTreeNodeRaw {
1762
+ key: number
1763
+ firstOffer: number
1764
+ parent: number
1765
+ left: number
1766
+ right: number
1767
+ }
1768
+
1769
+ export interface OfferNodeRaw {
1770
+ register: number
1771
+ amount: BN
1772
+ userVaultPointer: number
1773
+ expiryAt: number
1774
+ createdAt: number
1775
+ virtualOffer: boolean
1776
+ orderTypeFlag: number
1777
+ fillOrKill: boolean
1778
+ }
1779
+
1780
+ export interface UserEscrowNodeRaw {
1781
+ user: web3.PublicKey
1782
+ yieldIndex: number
1783
+ ptAmount: BN
1784
+ syAmount: BN
1785
+ ytAmount: BN
1786
+ staged: number
1787
+ }
1788
+
735
1789
  function deserializeAnchorizedPNum(x: AnchorizedPNum): number {
736
1790
  return parseFloat(PreciseNumber.fromRaw(x[0]).valueString)
737
1791
  }
738
1792
 
1793
+ /** Convert PreciseNumber (Number type in Rust) from Anchor format to raw 256-bit bigint */
1794
+ export function anchorizedPNumToRawBigint(pnum: AnchorizedPNum): bigint {
1795
+ const bnArray = pnum[0]
1796
+ let val = 0n
1797
+ for (let i = 0; i < 4; i++) {
1798
+ val += BigInt(bnArray[i].toString()) << BigInt(i * 64)
1799
+ }
1800
+ return val
1801
+ }
1802
+
739
1803
  /** Fetch the exchange rate of a JitoRestaking vault's VRT to JitoSOL */
740
1804
  async function fetchJitoVaultData({
741
1805
  connection,
@@ -745,30 +1809,7 @@ async function fetchJitoVaultData({
745
1809
  vaultAddress: web3.PublicKey
746
1810
  }) {
747
1811
  const vaultAccountInfo = await connection.getAccountInfo(vaultAddress)
748
- const d = vaultAccountInfo.data
749
-
750
- // the vault has an 8 byte discriminator at the beginning
751
- const discriminatorOffset = 8
752
- const vrtMintOffset = 32 + discriminatorOffset
753
- const vrtSupplyOffset = 96 + discriminatorOffset
754
- const jitoVaultTotalDepositsOffset = 104 + discriminatorOffset
755
- const mintBase = new web3.PublicKey(d.slice(vrtMintOffset, vrtMintOffset + 32))
756
-
757
- // For Borsh, numbers are serialized in little-endian format
758
- const jitoVaultTotalSharesBuffer = d.slice(vrtSupplyOffset, vrtSupplyOffset + 8)
759
- const jitoVaultTotalShares = new BN(jitoVaultTotalSharesBuffer, "le")
760
-
761
- const jitoVaultTotalDepositsBuffer = d.slice(jitoVaultTotalDepositsOffset, jitoVaultTotalDepositsOffset + 8)
762
- const jitoVaultTotalDeposits = new BN(jitoVaultTotalDepositsBuffer, "le")
763
-
764
- const jitoVaultTotalSharesD = new Decimal(jitoVaultTotalShares.toString())
765
- const jitoVaultTotalDepositsD = new Decimal(jitoVaultTotalDeposits.toString())
766
-
767
- const exchangeRate = jitoVaultTotalDepositsD.isZero()
768
- ? "1.0"
769
- : jitoVaultTotalDepositsD.div(jitoVaultTotalSharesD).toString()
770
-
771
- return { exchangeRate: parseFloat(exchangeRate), mintBase }
1812
+ return decodeJitoVaultData(vaultAccountInfo.data)
772
1813
  }
773
1814
 
774
1815
  async function fetchJitoSolToSolExchangeRate({
@@ -810,23 +1851,16 @@ export async function fetchPerenaStablePoolData({
810
1851
  connection: web3.Connection
811
1852
  perenaStablePool: web3.PublicKey
812
1853
  }) {
813
- const [lpMint, _] = web3.PublicKey.findProgramAddressSync(
814
- [perenaStablePool.toBuffer(), Buffer.from("liquidity")],
815
- new web3.PublicKey("NUMERUNsFCP3kuNmWZuXtm1AaQCPj9uw6Guv2Ekoi5P"),
816
- )
1854
+ const lpMint = getPerenaLpMint(perenaStablePool)
817
1855
 
818
1856
  const [accountInfo, lpMintInfo] = await connection.getMultipleAccountsInfo([perenaStablePool, lpMint])
819
- const lpMintDeserialized = MintLayout.decode(lpMintInfo.data)
820
- const d = accountInfo.data
821
-
822
- const discriminatorOffset = 8
823
- const invTOffset = discriminatorOffset + 32 + 32 + 32 + 32 // 4 Pubkeys before invT
824
- const invTBuffer = d.slice(invTOffset, invTOffset + 8)
825
- const invT = Buffer.from(invTBuffer).readBigUInt64LE(0)
826
1857
 
827
- const exchangeRate = new Decimal(invT.toString()).div(new Decimal(lpMintDeserialized.supply.toString())).toString()
1858
+ const { lpSupply, invT, exchangeRate } = getPerenaStablePoolData({
1859
+ perenaStablePoolData: accountInfo.data,
1860
+ lpMintData: lpMintInfo.data,
1861
+ })
828
1862
 
829
- return { lpSupply: lpMintDeserialized.supply, invT, exchangeRate, lpMint }
1863
+ return { lpSupply, invT, exchangeRate, lpMint }
830
1864
  }
831
1865
 
832
1866
  /**
@@ -880,53 +1914,19 @@ export async function fetchJupiterPerpsIndex({
880
1914
  lastAumUsd: BN
881
1915
  currentIndex: AnchorizedPNum
882
1916
  lastRealizedFeeUsdUpdateUnixTimestamp: number
883
- }): Promise<{
884
- index: number
885
- newState: {
886
- lastAumUsd: BN
887
- lastRealizedFeeUsd: BN
888
- lastFeeUsdResetUnixTimestamp: number
889
- lastRealizedFeeUsdUpdateUnixTimestamp: number
890
- }
891
- }> {
892
- // Decode pool account using helper that leverages Anchor 0.29.0
1917
+ }): Promise<ReturnType<typeof calculateJupiterPerpsIndex>> {
893
1918
  const accountInfo = await connection.getAccountInfo(pool)
894
- const account: any = decodeJupiterPerpsPoolAccount(accountInfo?.data as Buffer)
895
- const SECONDS_PER_YEAR = 365 * 24 * 60 * 60
896
-
897
- let newFeesBn: BN
898
-
899
- if (lastRealizedFeeUsdUpdateUnixTimestamp === 0) {
900
- newFeesBn = new BN(account.poolApr.realizedFeeUsd.toString())
901
- } else if (Number(account.poolApr.lastUpdated) > lastFeeUsdResetUnixTimestamp) {
902
- const timeBetweenResets = Number(account.poolApr.lastUpdated) - lastFeeUsdResetUnixTimestamp
903
- const feeAprBps = new BN(account.poolApr.feeAprBps)
904
- const estTotalFees =
905
- lastAumUsd.gt(new BN(0)) && timeBetweenResets > 0
906
- ? feeAprBps.mul(lastAumUsd).mul(new BN(timeBetweenResets)).div(new BN(SECONDS_PER_YEAR)).div(new BN(10_000))
907
- : new BN(0)
908
-
909
- const missingFees = estTotalFees.sub(lastRealizedFeeUsd)
910
- const feesSinceReset = new BN(account.poolApr.realizedFeeUsd.toString())
911
- newFeesBn = missingFees.add(feesSinceReset)
912
- } else {
913
- newFeesBn = new BN(account.poolApr.realizedFeeUsd.toString()).sub(lastRealizedFeeUsd)
914
- }
915
1919
 
916
- const aumUsd = new BN(account.aumUsd.toString())
917
- const indexIncrease = new Decimal(newFeesBn.toString()).div(new Decimal(aumUsd.toString())).toNumber()
918
- const currentIndexNum = parseFloat(PreciseNumber.fromRaw(currentIndex[0]).valueString)
919
- const nextIndex = currentIndexNum + indexIncrease
920
-
921
- return {
922
- index: nextIndex,
923
- newState: {
924
- lastAumUsd: aumUsd,
925
- lastRealizedFeeUsd: new BN(account.poolApr.realizedFeeUsd.toString()),
926
- lastFeeUsdResetUnixTimestamp: Number(account.poolApr.lastUpdated),
927
- lastRealizedFeeUsdUpdateUnixTimestamp: Math.floor(Date.now() / 1000),
1920
+ return calculateJupiterPerpsIndex(
1921
+ { pool: accountInfo.data },
1922
+ {
1923
+ lastFeeUsdResetUnixTimestamp,
1924
+ lastRealizedFeeUsd,
1925
+ lastAumUsd,
1926
+ currentIndex,
1927
+ lastRealizedFeeUsdUpdateUnixTimestamp,
928
1928
  },
929
- }
1929
+ )
930
1930
  }
931
1931
 
932
1932
  export async function fetchPyth(connection: web3.Connection): Promise<string> {
@@ -975,13 +1975,11 @@ export async function fetchFragmetricIndex({
975
1975
  connection: web3.Connection
976
1976
  fragmetricFund: web3.PublicKey
977
1977
  }) {
978
- const account = await connection.getAccountInfo(fragmetricFund)
979
- const coder = new BorshCoder(FragmetricIdl as Idl)
980
- const data = coder.accounts.decode("FundAccount", account.data)
981
- const index = Number(data.one_receipt_token_as_sol) / Number(10 ** data.receipt_token_decimals)
1978
+ const fragmetricFundRaw = await connection.getAccountInfo(fragmetricFund)
982
1979
 
983
- const receiptTokenMint = new web3.PublicKey(data.receipt_token_mint)
984
- const wrappedTokenMint = new web3.PublicKey(data.wrapped_token.mint)
1980
+ const { index, receiptTokenMint, wrappedTokenMint } = calculateFragmetricIndex({
1981
+ fragmetricFund: fragmetricFundRaw.data,
1982
+ })
985
1983
 
986
1984
  return { index, receiptTokenMint, wrappedTokenMint }
987
1985
  }
@@ -992,21 +1990,24 @@ export async function fetchJupiterLendIndex({
992
1990
  }: {
993
1991
  connection: web3.Connection
994
1992
  jupiterLendAccount: web3.PublicKey
995
- }) {
1993
+ }): Promise<ReturnType<typeof calculateJupiterLendIndex> & { rateModel: web3.PublicKey }> {
996
1994
  const account = await connection.getAccountInfo(jupiterLendAccount)
997
- const coder = new BorshCoder(JupiterLendIdl as Idl)
998
- const data = coder.accounts.decode("Lending", account.data)
999
- const index = Number(data.token_exchange_price) / Number(10 ** 12)
1000
- const tokenReservesLiquidity = data.token_reserves_liquidity
1001
- const lendingSupplyPosition = data.supply_position_on_liquidity
1002
- const rewardsRateModel = data.rewards_rate_model
1995
+
1003
1996
  const rateModel = jupiterLendAccount.equals(new web3.PublicKey("BeAqbxfrcXmzEYT2Ra62oW2MqkuFDHaCtps47Mzg6Zj3"))
1004
1997
  ? new web3.PublicKey("Acvyi9HBGmqh3Exe1N4PjBVyY8fokq2AdC6fSLqV6KSo")
1005
1998
  : new web3.PublicKey("6iHHKAK9Mqjn57CVmWe4szAPyTH8s8pniXSj6vWaKW5r")
1006
1999
 
1007
- console.log("token reserves liquidity in fetch jupiter lend index", tokenReservesLiquidity.toBase58())
2000
+ const { index, baseTokenMint, tokenReservesLiquidity, lendingSupplyPosition, rewardsRateModel } =
2001
+ calculateJupiterLendIndex({ jupiterLend: account.data })
1008
2002
 
1009
- return { index, baseTokenMint: data.mint, tokenReservesLiquidity, lendingSupplyPosition, rewardsRateModel, rateModel }
2003
+ return {
2004
+ rateModel,
2005
+ index,
2006
+ baseTokenMint,
2007
+ tokenReservesLiquidity,
2008
+ lendingSupplyPosition,
2009
+ rewardsRateModel,
2010
+ }
1010
2011
  }
1011
2012
 
1012
2013
  export async function fetchKaminoVaultIndex({
@@ -1053,18 +2054,10 @@ export async function fetchFragmetricSupportedTokenIndex({
1053
2054
  connection: web3.Connection
1054
2055
  fragmetricFund: web3.PublicKey
1055
2056
  index: number
1056
- }) {
2057
+ }): Promise<ReturnType<typeof calculateFragmetricSupportedTokenIndex>> {
1057
2058
  const account = await connection.getAccountInfo(fragmetricFund)
1058
- const coder = new BorshCoder(FragmetricIdl as Idl)
1059
- const data = coder.accounts.decode("FundAccount", account.data)
1060
- const syIndex =
1061
- Number(10 ** data.supported_tokens[index].decimals) /
1062
- Number(data.supported_tokens[index].one_token_as_receipt_token)
1063
-
1064
- const receiptTokenMint = new web3.PublicKey(data.receipt_token_mint)
1065
- const wrappedTokenMint = new web3.PublicKey(data.wrapped_token.mint)
1066
2059
 
1067
- return { index: syIndex, receiptTokenMint, wrappedTokenMint }
2060
+ return calculateFragmetricSupportedTokenIndex({ fragmetricFund: account.data }, index)
1068
2061
  }
1069
2062
 
1070
2063
  interface AccountsInterface {
@@ -1090,14 +2083,14 @@ export async function fetchMeteoraIndex({
1090
2083
  try {
1091
2084
  const VIRTUAL_PRICE_PRECISION = new BN(100_000_000)
1092
2085
 
1093
- const pool = await decodePoolAccount(connection, accounts.pool)
2086
+ const pool = await fetchPoolAccount(connection, accounts.pool)
1094
2087
 
1095
2088
  const poolMint = await getMint(connection, pool.lpMint)
1096
2089
  const poolLpSupply = new BN(poolMint.supply.toString())
1097
2090
  const poolLpDecimals = Number(poolMint.decimals.toString())
1098
2091
 
1099
- const vaultA = await decodeVaultAccount(connection, accounts.vaultA)
1100
- const vaultB = await decodeVaultAccount(connection, accounts.vaultB)
2092
+ const vaultA = await fetchVaultAccount(connection, accounts.vaultA)
2093
+ const vaultB = await fetchVaultAccount(connection, accounts.vaultB)
1101
2094
 
1102
2095
  const vaultLpMintA = await getMint(connection, vaultA.lpMint)
1103
2096
  const vaultLpMintB = await getMint(connection, vaultB.lpMint)
@@ -1155,8 +2148,6 @@ export async function fetchAdrenaIndex({
1155
2148
  index: number
1156
2149
  }> {
1157
2150
  try {
1158
- const zero = new BN(0)
1159
-
1160
2151
  // Fetch all account data in a single RPC call
1161
2152
  const accountInfos = await connection.getMultipleAccountsInfo([
1162
2153
  accounts.pool,
@@ -1170,25 +2161,17 @@ export async function fetchAdrenaIndex({
1170
2161
  throw new Error("One or more Adrena accounts not found")
1171
2162
  }
1172
2163
 
1173
- // Decode accounts using adrena-idl package functions
1174
- const { poolAccount, custodyAccounts } = decodePoolAndCustodyAccounts(accountInfos)
1175
- // @ts-ignore
1176
- const currentTotalFees = calculateTotalFeesFromCustodies(custodyAccounts)
1177
-
1178
- let yieldIncrement: number
1179
- let aumValue = poolAccount.aumUsd.low
1180
-
1181
- if (aumValue.eq(zero)) {
1182
- yieldIncrement = 0
1183
- }
1184
-
1185
- let feeDifference = currentTotalFees.gte(previousTotalFees) ? currentTotalFees.sub(previousTotalFees) : zero
1186
-
1187
- yieldIncrement = feeDifference.toNumber() / aumValue.toNumber()
1188
-
1189
- let currentIndexNumber = parseFloat(PreciseNumber.fromRaw(currentIndex[0]).valueString)
1190
-
1191
- return { index: currentIndexNumber + yieldIncrement }
2164
+ return calculateAdrenaIndex(
2165
+ {
2166
+ pool: accountInfos[0].data,
2167
+ custody1: accountInfos[1].data,
2168
+ custody2: accountInfos[2].data,
2169
+ custody3: accountInfos[3].data,
2170
+ custody4: accountInfos[4].data,
2171
+ },
2172
+ previousTotalFees,
2173
+ currentIndex,
2174
+ )
1192
2175
  } catch (error) {
1193
2176
  throw error
1194
2177
  }
@@ -1201,26 +2184,14 @@ export async function fetchSanctumIndex({
1201
2184
  connection: web3.Connection
1202
2185
  accounts: AccountsInterface
1203
2186
  }): Promise<number> {
1204
- try {
1205
- const poolState = await decodePoolStateAccount(connection, accounts.poolState)
1206
- const lpMint = await getMint(connection, accounts.lpMint)
1207
-
1208
- const zeroBn = new BN(0)
1209
- const precision = new BN(10 ** lpMint.decimals)
1210
- const lpTokenSupplyBn = new BN(lpMint.supply.toString())
1211
- const poolTotalSolValueBn = new BN(poolState.totalSolValue.toString())
1212
-
1213
- if (lpTokenSupplyBn.eq(zeroBn) || poolTotalSolValueBn.eq(zeroBn)) {
1214
- return 1
1215
- }
1216
-
1217
- const exchangeRateBn = lpTokenSupplyBn.mul(precision).div(poolTotalSolValueBn)
1218
- const exchangeRate = new Decimal(exchangeRateBn.toString()).div(precision.toString()).toNumber()
2187
+ const [poolStateAccountRaw, lpMintAccountRaw] = await Promise.all(
2188
+ [accounts.poolState, accounts.lpMint].map((pk) => connection.getAccountInfo(pk)),
2189
+ )
1219
2190
 
1220
- return 1 / exchangeRate // We return inverse exchange rate
1221
- } catch (error) {
1222
- throw error
1223
- }
2191
+ return calculateSanctumIndex({
2192
+ poolStateAccountData: poolStateAccountRaw.data,
2193
+ lpMintAccountData: lpMintAccountRaw.data,
2194
+ })
1224
2195
  }
1225
2196
 
1226
2197
  export async function fetchSolsticeRedemptionRate({
@@ -1231,60 +2202,105 @@ export async function fetchSolsticeRedemptionRate({
1231
2202
  connection: web3.Connection
1232
2203
  yieldPool: web3.PublicKey
1233
2204
  vestingSchedule: web3.PublicKey
1234
- }): Promise<{
1235
- redemptionRate: number
1236
- totalAssets: string
1237
- sharesSupply: string
1238
- vestingAmount: string
1239
- totalVestedAssets: string
1240
- }> {
1241
- try {
1242
- // Fetch both accounts in a single RPC call for efficiency
1243
- const accountInfos = await connection.getMultipleAccountsInfo([yieldPool, vestingSchedule])
2205
+ }): Promise<ReturnType<typeof calculateSolsticeRedemptionRate>> {
2206
+ // Fetch both accounts in a single RPC call for efficiency
2207
+ const accountInfos = await connection.getMultipleAccountsInfo([yieldPool, vestingSchedule])
1244
2208
 
1245
- if (!accountInfos[0] || !accountInfos[1]) {
1246
- throw new Error("One or more Solstice accounts not found")
1247
- }
2209
+ if (!accountInfos[0] || !accountInfos[1]) {
2210
+ throw new Error("One or more Solstice accounts not found")
2211
+ }
1248
2212
 
1249
- // Decode accounts using solstice-idl package function
1250
- const { yieldPoolAccount, vestingScheduleAccount } = decodeYieldPoolAndVestingScheduleAccounts(accountInfos)
2213
+ return calculateSolsticeRedemptionRate({ yieldPool: accountInfos[0].data, vestingSchedule: accountInfos[1].data })
2214
+ }
1251
2215
 
1252
- // Extract fields for calculation (convert BN to bigint)
1253
- const poolTotalAssets = BigInt(yieldPoolAccount.total_assets.toString())
1254
- const poolSharesSupply = BigInt(yieldPoolAccount.shares_supply.toString())
1255
- const vestingAmount = BigInt(vestingScheduleAccount.vesting_amount.toString())
2216
+ const REFLECT_ORACLE_LEN = 17
2217
+ const REFLECT_MAX_STALENESS_SLOTS = 15000000
1256
2218
 
1257
- // Calculate total vested assets (mimicking the Rust logic)
1258
- let totalVestedAssets: bigint
1259
- if (vestingAmount === 0n) {
1260
- totalVestedAssets = poolTotalAssets // If no vesting amount, all assets are considered vested
1261
- } else {
1262
- const vestingStart = BigInt(vestingScheduleAccount.start_time.toString())
1263
- const vestingEnd = BigInt(vestingScheduleAccount.end_time.toString())
1264
- const currentTime = BigInt(Math.floor(Date.now() / 1000)) // Current Unix timestamp
1265
-
1266
- // Calculate unvested amount
1267
- let unvestedAmount: bigint
1268
- if (currentTime > vestingEnd) {
1269
- unvestedAmount = 0n // If current time passed the vesting end, no unvested amount
1270
- } else {
1271
- unvestedAmount = (vestingAmount * (vestingEnd - currentTime)) / (vestingEnd - vestingStart)
1272
- }
2219
+ export async function fetchReflectRedemptionRate({
2220
+ connection,
2221
+ oracle,
2222
+ }: {
2223
+ connection: web3.Connection
2224
+ oracle: web3.PublicKey
2225
+ }): Promise<number> {
2226
+ const accountInfo = await connection.getAccountInfo(oracle)
1273
2227
 
1274
- totalVestedAssets = poolTotalAssets - unvestedAmount
1275
- }
2228
+ if (!accountInfo) {
2229
+ throw new Error("Reflect oracle account not found")
2230
+ }
1276
2231
 
1277
- // Calculate redemption rate: (total_vested_assets + 1) / (pool_shares_supply + 1)
1278
- const redemptionRate = Number(totalVestedAssets + 1n) / Number(poolSharesSupply + 1n)
2232
+ if (accountInfo.data.length !== REFLECT_ORACLE_LEN) {
2233
+ throw new Error(`Reflect oracle account has invalid length: ${accountInfo.data.length}`)
2234
+ }
1279
2235
 
1280
- return {
1281
- redemptionRate,
1282
- totalAssets: poolTotalAssets.toString(),
1283
- sharesSupply: poolSharesSupply.toString(),
1284
- vestingAmount: vestingAmount.toString(),
1285
- totalVestedAssets: totalVestedAssets.toString(),
1286
- }
1287
- } catch (error) {
1288
- throw error
2236
+ const slot = Number(accountInfo.data.readBigUInt64LE(0))
2237
+ const price = Number(accountInfo.data.readBigUInt64LE(8))
2238
+ const precision = accountInfo.data.readUInt8(16)
2239
+
2240
+ const currentSlot = await connection.getSlot()
2241
+ if (slot > currentSlot) {
2242
+ throw new Error("Reflect oracle slot is ahead of the current slot")
1289
2243
  }
2244
+ const slotDelta = currentSlot - slot
2245
+
2246
+ if (slotDelta > REFLECT_MAX_STALENESS_SLOTS) {
2247
+ throw new Error("Reflect oracle data is stale")
2248
+ }
2249
+
2250
+ const scale = Math.pow(10, precision)
2251
+
2252
+ if (scale === 0) {
2253
+ throw new Error("Invalid oracle precision")
2254
+ }
2255
+
2256
+ return price / scale
2257
+ }
2258
+
2259
+ export async function fetchOreExchangeRate({
2260
+ connection,
2261
+ storeMint,
2262
+ stakeAccount,
2263
+ treasury,
2264
+ }: {
2265
+ connection: web3.Connection
2266
+ storeMint: web3.PublicKey
2267
+ stakeAccount: web3.PublicKey
2268
+ treasury: web3.PublicKey
2269
+ }): Promise<number> {
2270
+ // Fetch all accounts in parallel for efficiency
2271
+ const [storeMintInfo, stakeAccountInfo, treasuryAccountInfo] = await Promise.all([
2272
+ connection.getAccountInfo(storeMint),
2273
+ connection.getAccountInfo(stakeAccount),
2274
+ connection.getAccountInfo(treasury),
2275
+ ])
2276
+
2277
+ if (!storeMintInfo || !stakeAccountInfo || !treasuryAccountInfo) {
2278
+ throw new Error("One or more ORE accounts not found")
2279
+ }
2280
+
2281
+ return calculateOreExchangeRate({
2282
+ stakeAccount: stakeAccountInfo.data,
2283
+ treasuryAccount: treasuryAccountInfo.data,
2284
+ storeMint: storeMintInfo.data,
2285
+ })
2286
+ }
2287
+
2288
+ export async function fetchChainlinkRate({
2289
+ connection,
2290
+ priceFeed,
2291
+ }: {
2292
+ connection: web3.Connection
2293
+ priceFeed: web3.PublicKey
2294
+ }): Promise<number> {
2295
+ const accountInfo = await connection.getAccountInfo(priceFeed)
2296
+
2297
+ if (!accountInfo) {
2298
+ throw new Error("Chainlink price feed account not found")
2299
+ }
2300
+
2301
+ const { answer, header } = decodeChainlinkPriceAccount(accountInfo)
2302
+
2303
+ const scale = Math.pow(10, header.decimals)
2304
+
2305
+ return Number(answer) / scale
1290
2306
  }