@exponent-labs/exponent-fetcher 0.1.8 → 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.
@@ -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"
@@ -90,12 +92,6 @@ export function serializeAnchorizedPNumFromJson(pnum: AnchorizedPNum): Anchorize
90
92
  return { 0: serializedArray }
91
93
  }
92
94
 
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
95
  export function deserializeAnchorizedPNumFromJson(serialized: AnchorizedPNumJson): AnchorizedPNum {
100
96
  const bnArray = serialized[0].map((str) => new BN(str))
101
97
  return { 0: bnArray }
@@ -410,6 +406,7 @@ export class ExponentFetcher {
410
406
  })
411
407
 
412
408
  return marketsProgramAccounts
409
+ .filter(({ pubkey }) => !isMarketIgnored(pubkey.toBase58()))
413
410
  .map(({ account, pubkey }) => {
414
411
  try {
415
412
  return this.exponentClmmProgram.coder.accounts.decode("marketThree", account.data)
@@ -421,11 +418,16 @@ export class ExponentFetcher {
421
418
  }
422
419
 
423
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
+
424
426
  try {
425
427
  const m: MarketThreeRaw = await this.exponentClmmProgram.account.marketThree.fetch(address)
426
428
  return deserializeMarketThree(m)
427
429
  } catch (e) {
428
- console.error(`Error fetching market ${address.toBase58()}`)
430
+ console.error(`Error fetching market ${addressStr}`)
429
431
  console.error(e)
430
432
  throw e
431
433
  }
@@ -445,25 +447,74 @@ export class ExponentFetcher {
445
447
  ],
446
448
  })
447
449
 
448
- return ticksAccounts.map(({ account }) => deserializeMarketThreeTicks(account.data))
450
+ return ticksAccounts
451
+ .filter(({ pubkey }) => !isTicksIgnored(pubkey.toBase58()))
452
+ .map(({ account }) => deserializeMarketThreeTicks(account.data))
449
453
  }
450
454
 
451
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
+
452
461
  try {
453
462
  const m = (await this.connection.getAccountInfo(address)).data
454
463
  return deserializeMarketThreeTicks(m)
455
464
  } catch (e) {
456
- console.error(`Error fetching market ${address.toBase58()}`)
465
+ console.error(`Error fetching ticks ${addressStr}`)
457
466
  console.error(e)
458
467
  throw e
459
468
  }
460
469
  }
461
470
  }
462
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
+ */
463
481
  export function deserializeMarketThreeTicks(data: Buffer): Ticks {
464
- let offset = 8
465
- const MAX_TICK_NODES = 100
466
- const PERSONAL_TICK_YIELD_TRACKER_SIZE = 3
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
+ }
467
518
 
468
519
  const readPubkey = (): web3.PublicKey => {
469
520
  const pk = new web3.PublicKey(data.slice(offset, offset + 32))
@@ -471,9 +522,9 @@ export function deserializeMarketThreeTicks(data: Buffer): Ticks {
471
522
  return pk
472
523
  }
473
524
 
474
- const readNumber = (): number => {
475
- // Number is 32 bytes (4 x u64)
476
- const nums = []
525
+ /** Reads a PreciseNumber (Number type in Rust) as float - 32 bytes (4 x u64) */
526
+ const readPreciseNumberAsFloat = (): number => {
527
+ const nums: BN[] = []
477
528
  for (let i = 0; i < 4; i++) {
478
529
  nums.push(new BN(data.slice(offset + i * 8, offset + (i + 1) * 8), undefined, "le"))
479
530
  }
@@ -481,108 +532,92 @@ export function deserializeMarketThreeTicks(data: Buffer): Ticks {
481
532
  return parseFloat(PreciseNumber.fromRaw(nums).valueString)
482
533
  }
483
534
 
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
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
+ }
497
545
 
498
- const ticksTreeBump = data.readUInt32LE(offset)
499
- offset += 4
546
+ const skip = (bytes: number): void => {
547
+ offset += bytes
548
+ }
500
549
 
501
- const ticksTreeFreeIdx = data.readUInt32LE(offset)
502
- offset += 4
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
503
556
 
557
+ // ─── Parse tick nodes ─────────────────────────────────────────────────────
504
558
  const ticks: Tick[] = []
505
559
 
506
560
  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 = []
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 }[] = []
537
580
  for (let j = 0; j < PERSONAL_TICK_YIELD_TRACKER_SIZE; j++) {
538
- farms.push({ lastSeenIndex: readNumber() })
581
+ farms.push({ lastSeenIndex: readPreciseNumberAsFloat() })
539
582
  }
540
583
 
541
- // Parse EmissionYieldTrackers (3 trackers x 64 bytes each)
542
- const emissions = []
584
+ // EmissionYieldTrackers: 2 x EmissionYieldTracker(64 bytes) = 128 bytes
585
+ const emissions: { lastSeenIndex: number; lastPositionIndex: number }[] = []
543
586
  for (let j = 0; j < PERSONAL_TICK_YIELD_TRACKER_SIZE; j++) {
544
- const lastSeenIndex = readNumber()
545
- const lastPositionIndex = readNumber()
546
- emissions.push({ lastSeenIndex, lastPositionIndex })
587
+ emissions.push({
588
+ lastSeenIndex: readPreciseNumberAsFloat(),
589
+ lastPositionIndex: readPreciseNumberAsFloat(),
590
+ })
547
591
  }
548
592
 
549
- // Parse last_split_epoch (u64)
550
- const lastSplitEpoch = data.readBigUInt64LE(offset)
551
- offset += 8
552
-
553
- // Skip padding (u64)
554
- offset += 8
593
+ const lastSplitEpoch = readU64() // 8 bytes
594
+ const frozenLiquidity = readU64() // 8 bytes
555
595
 
556
- if (apyBasePoints === 0) continue
557
596
  ticks.push({
558
597
  apyBasePoints,
559
598
  liquidityNet,
560
599
  feeGrowthOutsidePt,
561
600
  feeGrowthOutsideSy,
562
601
  liquidityGross,
563
- impliedRate,
602
+ impliedRate: spotPrice, // Legacy field name kept for compatibility
564
603
  principalPt,
565
604
  principalSy,
566
605
  principalShareSupply,
567
606
  farms,
568
607
  emissions,
569
608
  lastSplitEpoch,
609
+ frozenLiquidity,
570
610
  })
571
- // console.log(ticks)
572
611
  }
573
612
 
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
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
586
621
 
587
622
  return {
588
623
  ticksTree: ticks,
@@ -595,7 +630,76 @@ export function deserializeMarketThreeTicks(data: Buffer): Ticks {
595
630
  }
596
631
  }
597
632
 
598
- function deserializeMarketThree(m: MarketThreeRaw): MarketThree {
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
+
599
703
  return {
600
704
  addressLookupTable: m.addressLookupTable,
601
705
  mintSy: m.mintSy,
@@ -608,7 +712,7 @@ function deserializeMarketThree(m: MarketThreeRaw): MarketThree {
608
712
  selfAddress: m.selfAddress,
609
713
  syProgram: m.syProgram,
610
714
  statusFlags: m.statusFlags,
611
- cpiSyAccounts: m.cpiSyAccounts,
715
+ cpiSyAccounts,
612
716
  isCurrentFlashSwap: m.isCurrentFlashSwap,
613
717
  lpFarm: m.lpFarm,
614
718
  mintYt: m.mintYt,
@@ -637,8 +741,9 @@ function deserializeMarketThree(m: MarketThreeRaw): MarketThree {
637
741
  syBalance: BigInt(m.financials.syBalance.toString()),
638
742
  liquidityBalance: BigInt(m.financials.liquidityBalance.toString()),
639
743
  },
640
- cpiCoreAccounts: m.cpiCoreAccounts,
744
+ cpiCoreAccounts,
641
745
  exponentCoreProgram: m.exponentCoreProgram,
746
+ seedId: m.seedId,
642
747
  }
643
748
  }
644
749
 
@@ -699,7 +804,7 @@ function deserializeLpPositionCLMM(x: LpPositionCLMMRaw): LpPositionCLMM {
699
804
  tickIdx: tracker.tickIdx,
700
805
  rightTickIdx: tracker.rightTickIdx,
701
806
  splitEpoch: BigInt(tracker.splitEpoch.toString()),
702
- lpShare: BigInt(tracker.lpShare.toString()),
807
+ lpShare: anchorizedPNumToRawBigint(tracker.lpShare),
703
808
  emissions: tracker.emissions.trackers.map((e) => ({
704
809
  staged: BigInt(e.staged.toString()),
705
810
  lastSeenIndex: parseFloat(PreciseNumber.fromRaw(e.lastSeenIndex[0]).valueString),
@@ -760,8 +865,8 @@ function deserializeOrderbook(data: Buffer): Orderbook {
760
865
  offset += 8
761
866
  const priceDecimals = data.readUint8(offset)
762
867
  offset += 1
763
- // Skip ConfigurationOptions padding: _placeholder_one[15] + _placeholder_two[32] + _placeholder_three[32] + _placeholder_four[32] = 111 bytes
764
- offset += 111
868
+ // Skip ConfigurationOptions padding: _placeholder_one[15] + _placeholder_two[32] + _placeholder_three[32] + _placeholder_four[32] + _reserved[1024] = 1135 bytes
869
+ offset += 1135
765
870
 
766
871
  // Pubkeys
767
872
  const vault = readPubkey()
@@ -775,7 +880,14 @@ function deserializeOrderbook(data: Buffer): Orderbook {
775
880
  const cpiAccountOrderbook = readPubkey()
776
881
  const admin = readPubkey()
777
882
 
778
- // Skip last_sy_exchange_rate (Number type = 32 bytes)
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
+ })()
779
891
  offset += 32
780
892
 
781
893
  // OrderbookFinancials struct
@@ -818,12 +930,13 @@ function deserializeOrderbook(data: Buffer): Orderbook {
818
930
  }
819
931
  // console.log("financials", financials)
820
932
  // ─── Parse RedBlackTree slab ───────────────────────────────────────────────
821
- // repr(C) gives: root: u32, pad to align NodeAllocator’s u64, then the NodeAllocator header
933
+ // RedBlackTree struct: root: u32, _padding: [u32; 3], allocator: NodeAllocator<...>
934
+ // Total before allocator = 4 + 12 = 16 bytes
822
935
 
823
936
  const root = data.readUInt32LE(offset)
824
937
  offset += 4
825
- const padTo8 = 8
826
- offset += padTo8
938
+ const padding = 12 // _padding: [u32; 3] in RedBlackTree struct
939
+ offset += padding
827
940
 
828
941
  // NodeAllocator<T=RBNode<u32,PriceNode>, N=MAX_PRICE_NODES, R=3>
829
942
  // header: size:u64, bump_index:u32, free_list_head:u32
@@ -868,9 +981,9 @@ function deserializeOrderbook(data: Buffer): Orderbook {
868
981
 
869
982
  const offersSize = Number(data.readBigUInt64LE(offset))
870
983
  offset += 8
871
- const _offersBump = data.readUInt32LE(offset)
984
+ const offersBumpIndex = data.readUInt32LE(offset)
872
985
  offset += 4
873
- const _offersFreeIdx = data.readUInt32LE(offset)
986
+ const offersFreeListHead = data.readUInt32LE(offset)
874
987
  offset += 4
875
988
 
876
989
  const offers: OfferNode[] = []
@@ -898,6 +1011,7 @@ function deserializeOrderbook(data: Buffer): Orderbook {
898
1011
  offset += 5 // reserved padding
899
1012
  if (userVaultPointer === 0) continue
900
1013
  offers.push({
1014
+ offerIndex: i + 1,
901
1015
  nextOfferPointer,
902
1016
  amount,
903
1017
  userVaultPointer,
@@ -929,7 +1043,12 @@ function deserializeOrderbook(data: Buffer): Orderbook {
929
1043
  offset += 4
930
1044
  const user = new web3.PublicKey(data.slice(offset, offset + 32))
931
1045
  offset += 32
932
- /*const yieldIndex = data.readBigUInt64LE(offset).toString();*/ 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
933
1052
  const ptAmount = data.readBigUInt64LE(offset)
934
1053
  offset += 8
935
1054
  const syAmount = data.readBigUInt64LE(offset)
@@ -941,8 +1060,8 @@ function deserializeOrderbook(data: Buffer): Orderbook {
941
1060
  const staged = data.readBigInt64LE(offset)
942
1061
  offset += 8
943
1062
  offset += 8 // reserved
944
- if (user.toBase58() == "11111111111111111111111111111111") continue
945
- userEscrows.push({ user, yieldIndex: 0, ptAmount, syAmount, ytAmount, stakedYtAmount, staged })
1063
+ // if (user.toBase58() == "11111111111111111111111111111111") continue
1064
+ userEscrows.push({ user, yieldIndex, ptAmount, syAmount, ytAmount, stakedYtAmount, staged })
946
1065
  }
947
1066
 
948
1067
  // ─── Finally, seed_id + signer_bump + reserved ─────────────────────────────
@@ -971,11 +1090,14 @@ function deserializeOrderbook(data: Buffer): Orderbook {
971
1090
  tokenEscrowYt,
972
1091
  tokenEscrowPt,
973
1092
  cpiAccountOrderbook,
1093
+ lastSyExchangeRate: lastSyExchangeRateRaw,
974
1094
  financials,
975
1095
  prices,
976
1096
  configurationOptions,
977
1097
  offers,
978
1098
  userEscrows,
1099
+ offersBumpIndex,
1100
+ offersFreeListHead,
979
1101
  }
980
1102
  }
981
1103
 
@@ -1161,6 +1283,7 @@ export interface MarketThree {
1161
1283
  }[]
1162
1284
  }
1163
1285
  liquidityNetBalanceLimits: LiquidityNetBalanceLimits
1286
+ seedId: number[]
1164
1287
  }
1165
1288
 
1166
1289
  export interface Ticks {
@@ -1195,12 +1318,14 @@ export interface Tick {
1195
1318
  principalSy: bigint
1196
1319
  apyBasePoints: number
1197
1320
  principalShareSupply: bigint
1198
- /** Farm yield trackers (3 trackers) */
1321
+ /** Farm yield trackers (2 trackers) */
1199
1322
  farms: { lastSeenIndex: number }[]
1200
- /** Emission yield trackers (3 trackers) */
1323
+ /** Emission yield trackers (2 trackers) */
1201
1324
  emissions: { lastSeenIndex: number; lastPositionIndex: number }[]
1202
1325
  /** Last split epoch for this tick */
1203
1326
  lastSplitEpoch: bigint
1327
+ /** Frozen liquidity that cannot be withdrawn */
1328
+ frozenLiquidity: bigint
1204
1329
  }
1205
1330
 
1206
1331
  export interface MarketThreeRaw {
@@ -1375,11 +1500,17 @@ export interface Orderbook {
1375
1500
  tokenEscrowPt: web3.PublicKey
1376
1501
  cpiAccountOrderbook: web3.PublicKey
1377
1502
  admin: web3.PublicKey
1503
+ /** Raw 256-bit PreciseNumber (12 decimals) for last SY exchange rate */
1504
+ lastSyExchangeRate: bigint
1378
1505
  configurationOptions: ConfigurationOptions
1379
1506
  financials: OrderbookFinancials
1380
1507
  prices: PriceTreeNode[]
1381
1508
  offers: OfferNode[]
1382
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
1383
1514
  }
1384
1515
 
1385
1516
  export interface KaminoSyMeta {
@@ -1453,7 +1584,7 @@ interface LpPositionCLMMRaw {
1453
1584
  tickIdx: number
1454
1585
  rightTickIdx: number
1455
1586
  splitEpoch: BN
1456
- lpShare: BN
1587
+ lpShare: AnchorizedPNum
1457
1588
  emissions: { trackers: { staged: BN; lastSeenIndex: AnchorizedPNum }[] }
1458
1589
  }[]
1459
1590
  }
@@ -1648,7 +1779,7 @@ export interface OfferNodeRaw {
1648
1779
 
1649
1780
  export interface UserEscrowNodeRaw {
1650
1781
  user: web3.PublicKey
1651
- yieldIndex: BN
1782
+ yieldIndex: number
1652
1783
  ptAmount: BN
1653
1784
  syAmount: BN
1654
1785
  ytAmount: BN
@@ -1659,6 +1790,16 @@ function deserializeAnchorizedPNum(x: AnchorizedPNum): number {
1659
1790
  return parseFloat(PreciseNumber.fromRaw(x[0]).valueString)
1660
1791
  }
1661
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
+
1662
1803
  /** Fetch the exchange rate of a JitoRestaking vault's VRT to JitoSOL */
1663
1804
  async function fetchJitoVaultData({
1664
1805
  connection,
@@ -2073,7 +2214,7 @@ export async function fetchSolsticeRedemptionRate({
2073
2214
  }
2074
2215
 
2075
2216
  const REFLECT_ORACLE_LEN = 17
2076
- const REFLECT_MAX_STALENESS_SLOTS = 150
2217
+ const REFLECT_MAX_STALENESS_SLOTS = 15000000
2077
2218
 
2078
2219
  export async function fetchReflectRedemptionRate({
2079
2220
  connection,
@@ -2143,3 +2284,23 @@ export async function fetchOreExchangeRate({
2143
2284
  storeMint: storeMintInfo.data,
2144
2285
  })
2145
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
2306
+ }
package/src/index.ts CHANGED
@@ -11,3 +11,4 @@ export * from "./utils/kamino"
11
11
  export * from "./utils/perena"
12
12
  export * from "./utils/jito"
13
13
  export * from "./utils/ore"
14
+ export * from "./constants"
package/tsconfig.json CHANGED
@@ -38,6 +38,7 @@
38
38
  { "path": "../jupiter-lend-idl" },
39
39
  { "path": "../kamino-vault-idl" },
40
40
  { "path": "../jupiter-perps-idl" },
41
- { "path": "../solstice-idl" }
41
+ { "path": "../solstice-idl" },
42
+ { "path": "../chainlink-idl" }
42
43
  ]
43
44
  }