@exponent-labs/exponent-fetcher 0.1.8 → 0.9.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -14,6 +14,7 @@ import {
14
14
  decodeCustodyAccount,
15
15
  decodePoolAndCustodyAccounts,
16
16
  } from "@exponent-labs/adrena-idl"
17
+ import { decodeChainlinkPriceAccount } from "@exponent-labs/chainlink-idl"
17
18
  import {
18
19
  PROGRAM_ID as EXPONENT_CLMM_PROGRAM_ID,
19
20
  ExponentClmm,
@@ -75,6 +76,7 @@ import { PerenaStandard } from "@exponent-labs/perena-sy-idl"
75
76
  import { PreciseNumber } from "@exponent-labs/precise-number"
76
77
  import { decodeYieldPoolAndVestingScheduleAccounts } from "@exponent-labs/solstice-idl"
77
78
 
79
+ import { IgnoredClmmEntityError, isMarketIgnored, isTicksIgnored } from "./constants"
78
80
  import { calculateAdrenaIndex } from "./utils/adrena"
79
81
  import { calculateFragmetricIndex, calculateFragmetricSupportedTokenIndex } from "./utils/fragmetric"
80
82
  import { decodeJitoVaultData } from "./utils/jito"
@@ -85,17 +87,13 @@ import { getPerenaLpMint, getPerenaStablePoolData } from "./utils/perena"
85
87
  import { calculateSanctumIndex } from "./utils/sanctum"
86
88
  import { calculateSolsticeRedemptionRate } from "./utils/solstice"
87
89
 
90
+ const KAMINO_VAULT_GLOBAL_CONFIG = new web3.PublicKey("BKyTcUe6daNG8HbgBix2ugdRHbykG2dK9hPBBqhUyoEX")
91
+
88
92
  export function serializeAnchorizedPNumFromJson(pnum: AnchorizedPNum): AnchorizedPNumJson {
89
93
  const serializedArray = pnum[0].map((bn) => bn.toString())
90
94
  return { 0: serializedArray }
91
95
  }
92
96
 
93
- function readU128LE(buf: Buffer, offset: number): bigint {
94
- const lo = buf.readBigUInt64LE(offset)
95
- const hi = buf.readBigUInt64LE(offset + 8)
96
- return (hi << 64n) + lo
97
- }
98
-
99
97
  export function deserializeAnchorizedPNumFromJson(serialized: AnchorizedPNumJson): AnchorizedPNum {
100
98
  const bnArray = serialized[0].map((str) => new BN(str))
101
99
  return { 0: bnArray }
@@ -410,6 +408,7 @@ export class ExponentFetcher {
410
408
  })
411
409
 
412
410
  return marketsProgramAccounts
411
+ .filter(({ pubkey }) => !isMarketIgnored(pubkey.toBase58()))
413
412
  .map(({ account, pubkey }) => {
414
413
  try {
415
414
  return this.exponentClmmProgram.coder.accounts.decode("marketThree", account.data)
@@ -421,11 +420,16 @@ export class ExponentFetcher {
421
420
  }
422
421
 
423
422
  async fetchMarketThree(address: web3.PublicKey): Promise<MarketThree> {
423
+ const addressStr = address.toBase58()
424
+ if (isMarketIgnored(addressStr)) {
425
+ throw new IgnoredClmmEntityError("market", addressStr)
426
+ }
427
+
424
428
  try {
425
429
  const m: MarketThreeRaw = await this.exponentClmmProgram.account.marketThree.fetch(address)
426
430
  return deserializeMarketThree(m)
427
431
  } catch (e) {
428
- console.error(`Error fetching market ${address.toBase58()}`)
432
+ console.error(`Error fetching market ${addressStr}`)
429
433
  console.error(e)
430
434
  throw e
431
435
  }
@@ -445,25 +449,74 @@ export class ExponentFetcher {
445
449
  ],
446
450
  })
447
451
 
448
- return ticksAccounts.map(({ account }) => deserializeMarketThreeTicks(account.data))
452
+ return ticksAccounts
453
+ .filter(({ pubkey }) => !isTicksIgnored(pubkey.toBase58()))
454
+ .map(({ account }) => deserializeMarketThreeTicks(account.data))
449
455
  }
450
456
 
451
457
  async fetchMarketThreeTicks(address: web3.PublicKey): Promise<Ticks> {
458
+ const addressStr = address.toBase58()
459
+ if (isTicksIgnored(addressStr)) {
460
+ throw new IgnoredClmmEntityError("ticks", addressStr)
461
+ }
462
+
452
463
  try {
453
464
  const m = (await this.connection.getAccountInfo(address)).data
454
465
  return deserializeMarketThreeTicks(m)
455
466
  } catch (e) {
456
- console.error(`Error fetching market ${address.toBase58()}`)
467
+ console.error(`Error fetching ticks ${addressStr}`)
457
468
  console.error(e)
458
469
  throw e
459
470
  }
460
471
  }
461
472
  }
462
473
 
474
+ /**
475
+ * Deserializes a CLMM Ticks account from raw buffer data.
476
+ *
477
+ * Layout matches Rust struct `Ticks` with `RedBlackTree<u32, Tick, 1000>`:
478
+ * - Discriminator: 8 bytes
479
+ * - RedBlackTree header: root(4) + padding(12) + size(8) + bump(4) + freeIdx(4) = 32 bytes
480
+ * - 1000 RBTree nodes, each: nodeHeader(16) + key(4) + padding(4) + Tick = variable bytes
481
+ * - Ticks footer: market(32) + feeGrowthPt(16) + feeGrowthSy(16) + prefixSum(8) + spotPrice(8) + currentTick(4) + padding(12) = 96 bytes
482
+ */
463
483
  export function deserializeMarketThreeTicks(data: Buffer): Ticks {
464
- let offset = 8
465
- const MAX_TICK_NODES = 100
466
- const PERSONAL_TICK_YIELD_TRACKER_SIZE = 3
484
+ const MAX_TICK_NODES = 1000
485
+ const PERSONAL_TICK_YIELD_TRACKER_SIZE = 2
486
+ let offset = 8 // Skip discriminator
487
+
488
+ // ─── Helper functions ─────────────────────────────────────────────────────
489
+ const readU64 = (): bigint => {
490
+ const val = data.readBigUInt64LE(offset)
491
+ offset += 8
492
+ return val
493
+ }
494
+
495
+ const readU128 = (): bigint => {
496
+ const lo = data.readBigUInt64LE(offset)
497
+ const hi = data.readBigUInt64LE(offset + 8)
498
+ offset += 16
499
+ return (hi << 64n) + lo
500
+ }
501
+
502
+ const readI128 = (): bigint => {
503
+ const lo = data.readBigUInt64LE(offset)
504
+ const hi = data.readBigInt64LE(offset + 8) // High part is signed
505
+ offset += 16
506
+ return (hi << 64n) + lo
507
+ }
508
+
509
+ const readF64 = (): number => {
510
+ const val = data.readDoubleLE(offset)
511
+ offset += 8
512
+ return val
513
+ }
514
+
515
+ const readU32 = (): number => {
516
+ const val = data.readUInt32LE(offset)
517
+ offset += 4
518
+ return val
519
+ }
467
520
 
468
521
  const readPubkey = (): web3.PublicKey => {
469
522
  const pk = new web3.PublicKey(data.slice(offset, offset + 32))
@@ -471,9 +524,9 @@ export function deserializeMarketThreeTicks(data: Buffer): Ticks {
471
524
  return pk
472
525
  }
473
526
 
474
- const readNumber = (): number => {
475
- // Number is 32 bytes (4 x u64)
476
- const nums = []
527
+ /** Reads a PreciseNumber (Number type in Rust) as float - 32 bytes (4 x u64) */
528
+ const readPreciseNumberAsFloat = (): number => {
529
+ const nums: BN[] = []
477
530
  for (let i = 0; i < 4; i++) {
478
531
  nums.push(new BN(data.slice(offset + i * 8, offset + (i + 1) * 8), undefined, "le"))
479
532
  }
@@ -481,108 +534,92 @@ export function deserializeMarketThreeTicks(data: Buffer): Ticks {
481
534
  return parseFloat(PreciseNumber.fromRaw(nums).valueString)
482
535
  }
483
536
 
484
- // ─── Parse RedBlackTree slab ───────────────────────────────────────────────
485
- // repr(C) gives: root: u32, pad to align NodeAllocator's u64, then the NodeAllocator header
486
-
487
- const root = data.readUInt32LE(offset)
488
- offset += 4
489
- const padTo8 = 12
490
- offset += padTo8
491
-
492
- // NodeAllocator<T=RBNode<u32,PriceNode>, N=MAX_PRICE_NODES, R=3>
493
- // header: size:u64, bump_index:u32, free_list_head:u32
494
-
495
- const tickTreeSize = Number(data.readBigUInt64LE(offset))
496
- offset += 8
537
+ /** Reads a PreciseNumber (Number type in Rust) as raw bigint - 32 bytes (4 x u64 = 256 bits) */
538
+ const readPreciseNumberAsBigint = (): bigint => {
539
+ let val = 0n
540
+ for (let i = 0; i < 4; i++) {
541
+ const chunk = data.readBigUInt64LE(offset + i * 8)
542
+ val += chunk << BigInt(i * 64)
543
+ }
544
+ offset += 32
545
+ return val
546
+ }
497
547
 
498
- const ticksTreeBump = data.readUInt32LE(offset)
499
- offset += 4
548
+ const skip = (bytes: number): void => {
549
+ offset += bytes
550
+ }
500
551
 
501
- const ticksTreeFreeIdx = data.readUInt32LE(offset)
502
- offset += 4
552
+ // ─── Parse RedBlackTree header ────────────────────────────────────────────
553
+ skip(4) // root: u32
554
+ skip(12) // padding to align NodeAllocator
555
+ skip(8) // size: u64
556
+ skip(4) // bump_index: u32
557
+ skip(4) // free_list_head: u32
503
558
 
559
+ // ─── Parse tick nodes ─────────────────────────────────────────────────────
504
560
  const ticks: Tick[] = []
505
561
 
506
562
  for (let i = 0; i < MAX_TICK_NODES; i++) {
507
- const left = data.readUInt32LE(offset)
508
- offset += 4
509
- const right = data.readUInt32LE(offset)
510
- offset += 4
511
- const parent = data.readUInt32LE(offset)
512
- offset += 4
513
- offset += 4 // skip color
514
-
515
- const apyBasePoints = data.readUInt32LE(offset)
516
- offset += 8
517
-
518
- const feeGrowthOutsidePt = readU128LE(data, offset)
519
- offset += 16
520
- const feeGrowthOutsideSy = readU128LE(data, offset)
521
- offset += 16
522
- const liquidityNet = data.readBigInt64LE(offset)
523
- offset += 8
524
- const liquidityGross = data.readBigInt64LE(offset)
525
- offset += 8
526
- const impliedRate = data.readDoubleLE(offset)
527
- offset += 8
528
- const principalPt = data.readBigInt64LE(offset)
529
- offset += 8
530
- const principalSy = data.readBigInt64LE(offset)
531
- offset += 8
532
- const principalShareSupply = data.readBigInt64LE(offset)
533
- offset += 8
534
-
535
- // Parse FarmYieldTrackers (3 trackers x 32 bytes each)
536
- const farms = []
563
+ // RBNode header: left(4) + right(4) + parent(4) + color(4) = 16 bytes
564
+ skip(16)
565
+
566
+ // Key: u32 + padding to 8 bytes
567
+ const apyBasePoints = readU32()
568
+ skip(4) // padding
569
+
570
+ // Tick value (416 bytes total)
571
+ const feeGrowthOutsidePt = readU128() // 16 bytes
572
+ const feeGrowthOutsideSy = readU128() // 16 bytes
573
+ const liquidityNet = readI128() // 16 bytes
574
+ const liquidityGross = readU64() // 8 bytes
575
+ const spotPrice = readF64() // 8 bytes
576
+ const principalPt = readU64() // 8 bytes
577
+ const principalSy = readU64() // 8 bytes
578
+ const principalShareSupply = readPreciseNumberAsBigint() // 32 bytes - kept as bigint for arithmetic
579
+
580
+ // FarmYieldTrackers: 2 x FarmYieldTracker(32 bytes) = 64 bytes
581
+ const farms: { lastSeenIndex: number }[] = []
537
582
  for (let j = 0; j < PERSONAL_TICK_YIELD_TRACKER_SIZE; j++) {
538
- farms.push({ lastSeenIndex: readNumber() })
583
+ farms.push({ lastSeenIndex: readPreciseNumberAsFloat() })
539
584
  }
540
585
 
541
- // Parse EmissionYieldTrackers (3 trackers x 64 bytes each)
542
- const emissions = []
586
+ // EmissionYieldTrackers: 2 x EmissionYieldTracker(64 bytes) = 128 bytes
587
+ const emissions: { lastSeenIndex: number; lastPositionIndex: number }[] = []
543
588
  for (let j = 0; j < PERSONAL_TICK_YIELD_TRACKER_SIZE; j++) {
544
- const lastSeenIndex = readNumber()
545
- const lastPositionIndex = readNumber()
546
- emissions.push({ lastSeenIndex, lastPositionIndex })
589
+ emissions.push({
590
+ lastSeenIndex: readPreciseNumberAsFloat(),
591
+ lastPositionIndex: readPreciseNumberAsFloat(),
592
+ })
547
593
  }
548
594
 
549
- // Parse last_split_epoch (u64)
550
- const lastSplitEpoch = data.readBigUInt64LE(offset)
551
- offset += 8
552
-
553
- // Skip padding (u64)
554
- offset += 8
595
+ const lastSplitEpoch = readU64() // 8 bytes
596
+ const frozenLiquidity = readU64() // 8 bytes
555
597
 
556
- if (apyBasePoints === 0) continue
557
598
  ticks.push({
558
599
  apyBasePoints,
559
600
  liquidityNet,
560
601
  feeGrowthOutsidePt,
561
602
  feeGrowthOutsideSy,
562
603
  liquidityGross,
563
- impliedRate,
604
+ impliedRate: spotPrice, // Legacy field name kept for compatibility
564
605
  principalPt,
565
606
  principalSy,
566
607
  principalShareSupply,
567
608
  farms,
568
609
  emissions,
569
610
  lastSplitEpoch,
611
+ frozenLiquidity,
570
612
  })
571
- // console.log(ticks)
572
613
  }
573
614
 
574
- const market = readPubkey()
575
- const feeGrowthIndexGlobalPt = readU128LE(data, offset)
576
- offset += 16
577
- const feeGrowthIndexGlobalSy = readU128LE(data, offset)
578
- offset += 16
579
- const currentPrefixSum = data.readBigUInt64LE(offset) // Active liquidity at current tick
580
- offset += 8
581
- const currentSpotPrice = data.readDoubleLE(offset)
582
- offset += 8
583
- const currentTick = data.readUint32LE(offset)
584
- offset += 4
585
- offset += 12 // padding
615
+ // ─── Parse Ticks footer ───────────────────────────────────────────────────
616
+ const market = readPubkey() // 32 bytes
617
+ const feeGrowthIndexGlobalPt = readU128() // 16 bytes
618
+ const feeGrowthIndexGlobalSy = readU128() // 16 bytes
619
+ const currentPrefixSum = readU64() // 8 bytes
620
+ const currentSpotPrice = readF64() // 8 bytes
621
+ const currentTick = readU32() // 4 bytes
622
+ skip(12) // padding
586
623
 
587
624
  return {
588
625
  ticksTree: ticks,
@@ -595,7 +632,76 @@ export function deserializeMarketThreeTicks(data: Buffer): Ticks {
595
632
  }
596
633
  }
597
634
 
598
- function deserializeMarketThree(m: MarketThreeRaw): MarketThree {
635
+ /** Decoded account may use snake_case (from JSON IDL); normalize to camelCase for app use. */
636
+ function normalizeCpiContext(a: { altIndex?: number; alt_index?: number; isSigner?: boolean; is_signer?: boolean; isWritable?: boolean; is_writable?: boolean }): { altIndex: number; isSigner: boolean; isWritable: boolean } {
637
+ return {
638
+ altIndex: a.altIndex ?? (a as { alt_index?: number }).alt_index ?? 0,
639
+ isSigner: a.isSigner ?? (a as { is_signer?: boolean }).is_signer ?? false,
640
+ isWritable: a.isWritable ?? (a as { is_writable?: boolean }).is_writable ?? false,
641
+ }
642
+ }
643
+
644
+ function normalizeCpiAccountIndexes(
645
+ raw: {
646
+ getSyState?: unknown[]
647
+ get_sy_state?: unknown[]
648
+ withdrawSy?: unknown[]
649
+ withdraw_sy?: unknown[]
650
+ depositSy?: unknown[]
651
+ deposit_sy?: unknown[]
652
+ claimEmission?: unknown[][]
653
+ claim_emission?: unknown[][]
654
+ getPositionState?: unknown[]
655
+ get_position_state?: unknown[]
656
+ }
657
+ ): CpiAccountIndexes {
658
+ const arr = (key: string, snake: string) => {
659
+ const a = (raw as Record<string, unknown>)[key] ?? (raw as Record<string, unknown>)[snake]
660
+ return Array.isArray(a) ? a.map((x) => normalizeCpiContext(x as Record<string, unknown>)) : []
661
+ }
662
+ const arr2 = (key: string, snake: string) => {
663
+ const a = (raw as Record<string, unknown>)[key] ?? (raw as Record<string, unknown>)[snake]
664
+ return Array.isArray(a) ? a.map((inner) => (Array.isArray(inner) ? inner.map((x) => normalizeCpiContext(x as Record<string, unknown>)) : [])) : []
665
+ }
666
+ return {
667
+ getSyState: arr("getSyState", "get_sy_state"),
668
+ withdrawSy: arr("withdrawSy", "withdraw_sy"),
669
+ depositSy: arr("depositSy", "deposit_sy"),
670
+ claimEmission: arr2("claimEmission", "claim_emission"),
671
+ getPositionState: arr("getPositionState", "get_position_state"),
672
+ }
673
+ }
674
+
675
+ function normalizeMarketCpiCoreIndexes(
676
+ raw: {
677
+ stripSy?: unknown[]
678
+ strip_sy?: unknown[]
679
+ mergeSy?: unknown[]
680
+ merge_sy?: unknown[]
681
+ }
682
+ ): MarketCpiCoreIndexes {
683
+ const arr = (key: string, snake: string) => {
684
+ const a = (raw as Record<string, unknown>)[key] ?? (raw as Record<string, unknown>)[snake]
685
+ return Array.isArray(a) ? a.map((x) => normalizeCpiContext(x as Record<string, unknown>)) : []
686
+ }
687
+ return {
688
+ stripSy: arr("stripSy", "strip_sy"),
689
+ mergeSy: arr("mergeSy", "merge_sy"),
690
+ }
691
+ }
692
+
693
+ export function deserializeMarketThree(m: MarketThreeRaw): MarketThree {
694
+ const rawCpiSy = m.cpiSyAccounts ?? (m as unknown as { cpi_sy_accounts?: unknown }).cpi_sy_accounts
695
+ const rawCpiCore = m.cpiCoreAccounts ?? (m as unknown as { cpi_core_accounts?: unknown }).cpi_core_accounts
696
+ const cpiSyAccounts =
697
+ rawCpiSy != null && typeof rawCpiSy === "object"
698
+ ? normalizeCpiAccountIndexes(rawCpiSy as Parameters<typeof normalizeCpiAccountIndexes>[0])
699
+ : (m.cpiSyAccounts ?? { getSyState: [], withdrawSy: [], depositSy: [], claimEmission: [], getPositionState: [] })
700
+ const cpiCoreAccounts =
701
+ rawCpiCore != null && typeof rawCpiCore === "object"
702
+ ? normalizeMarketCpiCoreIndexes(rawCpiCore as Parameters<typeof normalizeMarketCpiCoreIndexes>[0])
703
+ : (m.cpiCoreAccounts ?? { stripSy: [], mergeSy: [] })
704
+
599
705
  return {
600
706
  addressLookupTable: m.addressLookupTable,
601
707
  mintSy: m.mintSy,
@@ -608,7 +714,7 @@ function deserializeMarketThree(m: MarketThreeRaw): MarketThree {
608
714
  selfAddress: m.selfAddress,
609
715
  syProgram: m.syProgram,
610
716
  statusFlags: m.statusFlags,
611
- cpiSyAccounts: m.cpiSyAccounts,
717
+ cpiSyAccounts,
612
718
  isCurrentFlashSwap: m.isCurrentFlashSwap,
613
719
  lpFarm: m.lpFarm,
614
720
  mintYt: m.mintYt,
@@ -637,8 +743,9 @@ function deserializeMarketThree(m: MarketThreeRaw): MarketThree {
637
743
  syBalance: BigInt(m.financials.syBalance.toString()),
638
744
  liquidityBalance: BigInt(m.financials.liquidityBalance.toString()),
639
745
  },
640
- cpiCoreAccounts: m.cpiCoreAccounts,
746
+ cpiCoreAccounts,
641
747
  exponentCoreProgram: m.exponentCoreProgram,
748
+ seedId: m.seedId,
642
749
  }
643
750
  }
644
751
 
@@ -699,7 +806,7 @@ function deserializeLpPositionCLMM(x: LpPositionCLMMRaw): LpPositionCLMM {
699
806
  tickIdx: tracker.tickIdx,
700
807
  rightTickIdx: tracker.rightTickIdx,
701
808
  splitEpoch: BigInt(tracker.splitEpoch.toString()),
702
- lpShare: BigInt(tracker.lpShare.toString()),
809
+ lpShare: anchorizedPNumToRawBigint(tracker.lpShare),
703
810
  emissions: tracker.emissions.trackers.map((e) => ({
704
811
  staged: BigInt(e.staged.toString()),
705
812
  lastSeenIndex: parseFloat(PreciseNumber.fromRaw(e.lastSeenIndex[0]).valueString),
@@ -760,8 +867,8 @@ function deserializeOrderbook(data: Buffer): Orderbook {
760
867
  offset += 8
761
868
  const priceDecimals = data.readUint8(offset)
762
869
  offset += 1
763
- // Skip ConfigurationOptions padding: _placeholder_one[15] + _placeholder_two[32] + _placeholder_three[32] + _placeholder_four[32] = 111 bytes
764
- offset += 111
870
+ // Skip ConfigurationOptions padding: _placeholder_one[15] + _placeholder_two[32] + _placeholder_three[32] + _placeholder_four[32] + _reserved[1024] = 1135 bytes
871
+ offset += 1135
765
872
 
766
873
  // Pubkeys
767
874
  const vault = readPubkey()
@@ -775,7 +882,14 @@ function deserializeOrderbook(data: Buffer): Orderbook {
775
882
  const cpiAccountOrderbook = readPubkey()
776
883
  const admin = readPubkey()
777
884
 
778
- // Skip last_sy_exchange_rate (Number type = 32 bytes)
885
+ // last_sy_exchange_rate (Number type = 32 bytes, PreciseNumber with 12 decimals)
886
+ const lastSyExchangeRateRaw = (() => {
887
+ let val = 0n
888
+ for (let i = 0; i < 4; i++) {
889
+ val += data.readBigUInt64LE(offset + i * 8) << BigInt(i * 64)
890
+ }
891
+ return val
892
+ })()
779
893
  offset += 32
780
894
 
781
895
  // OrderbookFinancials struct
@@ -818,12 +932,13 @@ function deserializeOrderbook(data: Buffer): Orderbook {
818
932
  }
819
933
  // console.log("financials", financials)
820
934
  // ─── Parse RedBlackTree slab ───────────────────────────────────────────────
821
- // repr(C) gives: root: u32, pad to align NodeAllocator’s u64, then the NodeAllocator header
935
+ // RedBlackTree struct: root: u32, _padding: [u32; 3], allocator: NodeAllocator<...>
936
+ // Total before allocator = 4 + 12 = 16 bytes
822
937
 
823
938
  const root = data.readUInt32LE(offset)
824
939
  offset += 4
825
- const padTo8 = 8
826
- offset += padTo8
940
+ const padding = 12 // _padding: [u32; 3] in RedBlackTree struct
941
+ offset += padding
827
942
 
828
943
  // NodeAllocator<T=RBNode<u32,PriceNode>, N=MAX_PRICE_NODES, R=3>
829
944
  // header: size:u64, bump_index:u32, free_list_head:u32
@@ -868,9 +983,9 @@ function deserializeOrderbook(data: Buffer): Orderbook {
868
983
 
869
984
  const offersSize = Number(data.readBigUInt64LE(offset))
870
985
  offset += 8
871
- const _offersBump = data.readUInt32LE(offset)
986
+ const offersBumpIndex = data.readUInt32LE(offset)
872
987
  offset += 4
873
- const _offersFreeIdx = data.readUInt32LE(offset)
988
+ const offersFreeListHead = data.readUInt32LE(offset)
874
989
  offset += 4
875
990
 
876
991
  const offers: OfferNode[] = []
@@ -898,6 +1013,7 @@ function deserializeOrderbook(data: Buffer): Orderbook {
898
1013
  offset += 5 // reserved padding
899
1014
  if (userVaultPointer === 0) continue
900
1015
  offers.push({
1016
+ offerIndex: i + 1,
901
1017
  nextOfferPointer,
902
1018
  amount,
903
1019
  userVaultPointer,
@@ -929,7 +1045,12 @@ function deserializeOrderbook(data: Buffer): Orderbook {
929
1045
  offset += 4
930
1046
  const user = new web3.PublicKey(data.slice(offset, offset + 32))
931
1047
  offset += 32
932
- /*const yieldIndex = data.readBigUInt64LE(offset).toString();*/ offset += 32
1048
+ const yieldIndexRaw: AnchorizedPNum = [[new BN(0), new BN(0), new BN(0), new BN(0)]]
1049
+ for (let word = 0; word < 4; word++) {
1050
+ yieldIndexRaw[0][word] = new BN(data.subarray(offset + word * 8, offset + (word + 1) * 8), "le")
1051
+ }
1052
+ const yieldIndex = deserializeAnchorizedPNum(yieldIndexRaw)
1053
+ offset += 32
933
1054
  const ptAmount = data.readBigUInt64LE(offset)
934
1055
  offset += 8
935
1056
  const syAmount = data.readBigUInt64LE(offset)
@@ -941,8 +1062,8 @@ function deserializeOrderbook(data: Buffer): Orderbook {
941
1062
  const staged = data.readBigInt64LE(offset)
942
1063
  offset += 8
943
1064
  offset += 8 // reserved
944
- if (user.toBase58() == "11111111111111111111111111111111") continue
945
- userEscrows.push({ user, yieldIndex: 0, ptAmount, syAmount, ytAmount, stakedYtAmount, staged })
1065
+ // if (user.toBase58() == "11111111111111111111111111111111") continue
1066
+ userEscrows.push({ user, yieldIndex, ptAmount, syAmount, ytAmount, stakedYtAmount, staged })
946
1067
  }
947
1068
 
948
1069
  // ─── Finally, seed_id + signer_bump + reserved ─────────────────────────────
@@ -971,11 +1092,14 @@ function deserializeOrderbook(data: Buffer): Orderbook {
971
1092
  tokenEscrowYt,
972
1093
  tokenEscrowPt,
973
1094
  cpiAccountOrderbook,
1095
+ lastSyExchangeRate: lastSyExchangeRateRaw,
974
1096
  financials,
975
1097
  prices,
976
1098
  configurationOptions,
977
1099
  offers,
978
1100
  userEscrows,
1101
+ offersBumpIndex,
1102
+ offersFreeListHead,
979
1103
  }
980
1104
  }
981
1105
 
@@ -1161,6 +1285,7 @@ export interface MarketThree {
1161
1285
  }[]
1162
1286
  }
1163
1287
  liquidityNetBalanceLimits: LiquidityNetBalanceLimits
1288
+ seedId: number[]
1164
1289
  }
1165
1290
 
1166
1291
  export interface Ticks {
@@ -1195,12 +1320,14 @@ export interface Tick {
1195
1320
  principalSy: bigint
1196
1321
  apyBasePoints: number
1197
1322
  principalShareSupply: bigint
1198
- /** Farm yield trackers (3 trackers) */
1323
+ /** Farm yield trackers (2 trackers) */
1199
1324
  farms: { lastSeenIndex: number }[]
1200
- /** Emission yield trackers (3 trackers) */
1325
+ /** Emission yield trackers (2 trackers) */
1201
1326
  emissions: { lastSeenIndex: number; lastPositionIndex: number }[]
1202
1327
  /** Last split epoch for this tick */
1203
1328
  lastSplitEpoch: bigint
1329
+ /** Frozen liquidity that cannot be withdrawn */
1330
+ frozenLiquidity: bigint
1204
1331
  }
1205
1332
 
1206
1333
  export interface MarketThreeRaw {
@@ -1375,11 +1502,17 @@ export interface Orderbook {
1375
1502
  tokenEscrowPt: web3.PublicKey
1376
1503
  cpiAccountOrderbook: web3.PublicKey
1377
1504
  admin: web3.PublicKey
1505
+ /** Raw 256-bit PreciseNumber (12 decimals) for last SY exchange rate */
1506
+ lastSyExchangeRate: bigint
1378
1507
  configurationOptions: ConfigurationOptions
1379
1508
  financials: OrderbookFinancials
1380
1509
  prices: PriceTreeNode[]
1381
1510
  offers: OfferNode[]
1382
1511
  userEscrows: UserEscrowNode[]
1512
+ /** Next offer index that will be allocated (from NodeAllocator free list) */
1513
+ offersFreeListHead: number
1514
+ /** Bump index boundary for offers allocator */
1515
+ offersBumpIndex: number
1383
1516
  }
1384
1517
 
1385
1518
  export interface KaminoSyMeta {
@@ -1453,7 +1586,7 @@ interface LpPositionCLMMRaw {
1453
1586
  tickIdx: number
1454
1587
  rightTickIdx: number
1455
1588
  splitEpoch: BN
1456
- lpShare: BN
1589
+ lpShare: AnchorizedPNum
1457
1590
  emissions: { trackers: { staged: BN; lastSeenIndex: AnchorizedPNum }[] }
1458
1591
  }[]
1459
1592
  }
@@ -1648,7 +1781,7 @@ export interface OfferNodeRaw {
1648
1781
 
1649
1782
  export interface UserEscrowNodeRaw {
1650
1783
  user: web3.PublicKey
1651
- yieldIndex: BN
1784
+ yieldIndex: number
1652
1785
  ptAmount: BN
1653
1786
  syAmount: BN
1654
1787
  ytAmount: BN
@@ -1659,6 +1792,16 @@ function deserializeAnchorizedPNum(x: AnchorizedPNum): number {
1659
1792
  return parseFloat(PreciseNumber.fromRaw(x[0]).valueString)
1660
1793
  }
1661
1794
 
1795
+ /** Convert PreciseNumber (Number type in Rust) from Anchor format to raw 256-bit bigint */
1796
+ export function anchorizedPNumToRawBigint(pnum: AnchorizedPNum): bigint {
1797
+ const bnArray = pnum[0]
1798
+ let val = 0n
1799
+ for (let i = 0; i < 4; i++) {
1800
+ val += BigInt(bnArray[i].toString()) << BigInt(i * 64)
1801
+ }
1802
+ return val
1803
+ }
1804
+
1662
1805
  /** Fetch the exchange rate of a JitoRestaking vault's VRT to JitoSOL */
1663
1806
  async function fetchJitoVaultData({
1664
1807
  connection,
@@ -1881,28 +2024,70 @@ export async function fetchKaminoVaultIndex({
1881
2024
 
1882
2025
  const data = coder.accounts.decode("VaultState", account.data)
1883
2026
 
1884
- const activeReserves = data.vault_allocation_strategy
1885
- .map((r) => r.reserve)
1886
- .filter((reserve) => reserve.toBase58() !== web3.PublicKey.default.toBase58())
2027
+ const activeAllocations = data.vault_allocation_strategy
2028
+ .filter((allocation) => allocation.reserve.toBase58() !== web3.PublicKey.default.toBase58())
2029
+ const activeReserves = activeAllocations.map((allocation) => allocation.reserve)
1887
2030
 
1888
- console.log("activeReserves", activeReserves)
1889
- const activeReservesData = await connection.getMultipleAccountsInfo(activeReserves)
2031
+ const [activeReservesData, tokenMintInfo, sharesMintInfo] = await Promise.all([
2032
+ connection.getMultipleAccountsInfo(activeReserves),
2033
+ connection.getAccountInfo(data.token_mint),
2034
+ connection.getAccountInfo(data.shares_mint),
2035
+ ])
2036
+ const decodedReserves = activeReservesData.map((accountInfo, index) => {
2037
+ if (!accountInfo?.data) {
2038
+ throw new Error(`Missing Kamino reserve account ${activeReserves[index].toBase58()}`)
2039
+ }
2040
+ return Reserve.decode(accountInfo.data)
2041
+ })
2042
+ const collateralMintInfos = await connection.getMultipleAccountsInfo(
2043
+ decodedReserves.map((reserve) => reserve.collateral.mintPubkey),
2044
+ )
1890
2045
 
1891
2046
  const reserves = activeReserves.map((r, i) => {
1892
- console.log("r.data", activeReservesData[i].data)
2047
+ const reserveAccount = decodedReserves[i]
2048
+ const allocation = activeAllocations[i]
2049
+ const [lendingMarketAuthority] = web3.PublicKey.findProgramAddressSync(
2050
+ [Buffer.from("lma"), reserveAccount.lendingMarket.toBuffer()],
2051
+ new web3.PublicKey("KLend2g3cP87fffoy8q1mQqGKjrxjC8boSyAYavgmjD"),
2052
+ )
2053
+
1893
2054
  return {
1894
2055
  reserveAddress: r,
1895
- reserve: Reserve.decode(activeReservesData[i].data).lendingMarket,
2056
+ marketAddress: reserveAccount.lendingMarket,
2057
+ ctokenVault: allocation.ctoken_vault,
2058
+ lendingMarketAuthority,
2059
+ pythOracle: reserveAccount.config.tokenInfo.pythConfiguration.price,
2060
+ switchboardPriceOracle: reserveAccount.config.tokenInfo.switchboardConfiguration.priceAggregator,
2061
+ switchboardTwapOracle: reserveAccount.config.tokenInfo.switchboardConfiguration.twapAggregator,
2062
+ scopePrices: reserveAccount.config.tokenInfo.scopeConfiguration.priceFeed,
2063
+ reserveLiquiditySupply: reserveAccount.liquidity.supplyVault,
2064
+ reserveCollateralMint: reserveAccount.collateral.mintPubkey,
2065
+ reserveCollateralTokenProgram: collateralMintInfos[i]?.owner ?? web3.PublicKey.default,
1896
2066
  }
1897
2067
  })
1898
2068
 
1899
- console.log("reserves", reserves)
1900
2069
  const tokenVault = data.token_vault
1901
2070
  const tokenMint = data.token_mint
1902
2071
  const baseVaultAuthority = data.base_vault_authority
1903
2072
  const sharesMint = data.shares_mint
2073
+ const vaultLookupTable = data.vault_lookup_table ?? web3.PublicKey.default
2074
+ const tokenProgram = tokenMintInfo?.owner ?? web3.PublicKey.default
2075
+ const sharesTokenProgram = sharesMintInfo?.owner ?? web3.PublicKey.default
1904
2076
 
1905
- return { index: 1, tokenVault, tokenMint, baseVaultAuthority, sharesMint, reserves }
2077
+ return {
2078
+ index: 1,
2079
+ tokenVault,
2080
+ tokenMint,
2081
+ tokenProgram,
2082
+ // Kamino Vault withdraw expects the singleton program global config account.
2083
+ // It is not stored on VaultState, so fetch it from the known program-wide address.
2084
+ globalConfig: KAMINO_VAULT_GLOBAL_CONFIG,
2085
+ baseVaultAuthority,
2086
+ sharesMint,
2087
+ sharesTokenProgram,
2088
+ vaultLookupTable,
2089
+ reserves,
2090
+ }
1906
2091
  }
1907
2092
 
1908
2093
  export async function fetchFragmetricSupportedTokenIndex({
@@ -2073,7 +2258,7 @@ export async function fetchSolsticeRedemptionRate({
2073
2258
  }
2074
2259
 
2075
2260
  const REFLECT_ORACLE_LEN = 17
2076
- const REFLECT_MAX_STALENESS_SLOTS = 150
2261
+ const REFLECT_MAX_STALENESS_SLOTS = 15000000
2077
2262
 
2078
2263
  export async function fetchReflectRedemptionRate({
2079
2264
  connection,
@@ -2143,3 +2328,23 @@ export async function fetchOreExchangeRate({
2143
2328
  storeMint: storeMintInfo.data,
2144
2329
  })
2145
2330
  }
2331
+
2332
+ export async function fetchChainlinkRate({
2333
+ connection,
2334
+ priceFeed,
2335
+ }: {
2336
+ connection: web3.Connection
2337
+ priceFeed: web3.PublicKey
2338
+ }): Promise<number> {
2339
+ const accountInfo = await connection.getAccountInfo(priceFeed)
2340
+
2341
+ if (!accountInfo) {
2342
+ throw new Error("Chainlink price feed account not found")
2343
+ }
2344
+
2345
+ const { answer, header } = decodeChainlinkPriceAccount(accountInfo)
2346
+
2347
+ const scale = Math.pow(10, header.decimals)
2348
+
2349
+ return Number(answer) / scale
2350
+ }