@exponent-labs/exponent-fetcher 0.1.6 → 0.1.8

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 (50) hide show
  1. package/build/exponentFetcher.d.ts +303 -31
  2. package/build/exponentFetcher.js +666 -168
  3. package/build/exponentFetcher.js.map +1 -1
  4. package/build/index.d.ts +10 -0
  5. package/build/index.js +10 -0
  6. package/build/index.js.map +1 -1
  7. package/build/utils/adrena.d.ts +13 -0
  8. package/build/utils/adrena.js +29 -0
  9. package/build/utils/adrena.js.map +1 -0
  10. package/build/utils/fragmetric.d.ts +17 -0
  11. package/build/utils/fragmetric.js +23 -0
  12. package/build/utils/fragmetric.js.map +1 -0
  13. package/build/utils/jito.d.ts +7 -0
  14. package/build/utils/jito.js +29 -0
  15. package/build/utils/jito.js.map +1 -0
  16. package/build/utils/jupiter.d.ts +30 -0
  17. package/build/utils/jupiter.js +63 -0
  18. package/build/utils/jupiter.js.map +1 -0
  19. package/build/utils/kamino.d.ts +5 -0
  20. package/build/utils/kamino.js +10 -0
  21. package/build/utils/kamino.js.map +1 -0
  22. package/build/utils/meteora.d.ts +19 -0
  23. package/build/utils/meteora.js +36 -1
  24. package/build/utils/meteora.js.map +1 -1
  25. package/build/utils/ore.d.ts +74 -0
  26. package/build/utils/ore.js +217 -0
  27. package/build/utils/ore.js.map +1 -0
  28. package/build/utils/perena.d.ts +12 -0
  29. package/build/utils/perena.js +27 -0
  30. package/build/utils/perena.js.map +1 -0
  31. package/build/utils/sanctum.d.ts +6 -0
  32. package/build/utils/sanctum.js +26 -0
  33. package/build/utils/sanctum.js.map +1 -0
  34. package/build/utils/solstice.d.ts +12 -0
  35. package/build/utils/solstice.js +45 -0
  36. package/build/utils/solstice.js.map +1 -0
  37. package/package.json +20 -18
  38. package/src/exponentFetcher.ts +1077 -222
  39. package/src/index.ts +10 -0
  40. package/src/utils/adrena.ts +44 -0
  41. package/src/utils/fragmetric.ts +34 -0
  42. package/src/utils/jito.ts +30 -0
  43. package/src/utils/jupiter.ts +98 -0
  44. package/src/utils/kamino.ts +6 -0
  45. package/src/utils/meteora.ts +73 -1
  46. package/src/utils/ore.ts +322 -0
  47. package/src/utils/perena.ts +28 -0
  48. package/src/utils/sanctum.ts +24 -0
  49. package/src/utils/solstice.ts +51 -0
  50. package/tsconfig.json +2 -0
@@ -1,14 +1,31 @@
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 {
18
+ PROGRAM_ID as EXPONENT_CLMM_PROGRAM_ID,
19
+ ExponentClmm,
20
+ IDL as ExponentClmmIdl,
21
+ } from "@exponent-labs/exponent-clmm-idl"
7
22
  import { ExponentCore, IDL, PROGRAM_ID } from "@exponent-labs/exponent-idl"
23
+ import { IDL as EXPONENT_ORDERBOOK_IDL, ExponentOrderbook } from "@exponent-labs/exponent-orderbook-idl"
8
24
  import {
9
25
  AnchorizedPNum,
10
26
  AnchorizedPNumJson,
11
27
  CpiAccountIndexes,
28
+ ExponentCoreCpiIndexes,
12
29
  GenericSyMetaAccount,
13
30
  GenericSyMetaAccountRaw,
14
31
  InterfaceType,
@@ -16,6 +33,7 @@ import {
16
33
  JitoRestakingSyMetaAccountRaw,
17
34
  MarginfiSyMeta,
18
35
  MarginfiSyMetaRaw,
36
+ MarketCpiCoreIndexes,
19
37
  PerenaSyMetaAccount,
20
38
  PerenaSyMetaAccountRaw,
21
39
  SyEmissionRaw,
@@ -25,50 +43,59 @@ import {
25
43
  deserializeJitoRestakingSyMetaAccountRaw,
26
44
  deserializePerenaSyMetaAccountRaw,
27
45
  } 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"
46
+ import {
47
+ MAX_OFFERS,
48
+ MAX_PRICE_NODES,
49
+ MAX_USER_ESCROWS,
50
+ OfferNode,
51
+ PriceTreeNode,
52
+ UserEscrowNode,
53
+ } from "@exponent-labs/exponent-types"
31
54
  import { GenericStandard } from "@exponent-labs/generic-sy-idl"
32
55
  import { PROGRAM_ID as GENERIC_STANDARD_PROGRAM_ID, IDL as GenericStandardIdl } from "@exponent-labs/generic-sy-idl"
33
56
  import { PROGRAM_ID as JITO_RESTAKING_SY_PROGRAM_ID } from "@exponent-labs/jito-restaking-sy-idl"
34
57
  import { JitoRestakingStandard, IDL as JitoRestakingSyIdl } from "@exponent-labs/jito-restaking-sy-idl"
58
+ // Decode Jupiter Perps pool accounts via helper using Anchor 0.29.0
59
+ import { decodePoolAccount as decodeJupiterPerpsPoolAccount } from "@exponent-labs/jupiter-perps-idl"
35
60
  import { Obligation, Reserve } from "@exponent-labs/kamino-reserve-deserializer"
36
61
  import { PROGRAM_ID as KAMINO_LEND_PROGRAM_ID } from "@exponent-labs/kamino-reserve-deserializer"
37
62
  import { PROGRAM_ID as KAMINO_STANDARD_PROGRAM_ID } from "@exponent-labs/kamino-sy-idl"
38
63
  import { KaminoLendStandard } from "@exponent-labs/kamino-sy-idl"
39
64
  import { IDL as KaminoSyIdl } from "@exponent-labs/kamino-sy-idl"
65
+ import { IDL as KaminoVaultIdl } from "@exponent-labs/kamino-vault-idl"
40
66
  import {
41
67
  PROGRAM_ID as MARGINFI_SY_PROGRAM_ID,
42
68
  MarginfiStandard,
43
69
  IDL as MarginfiSyIdl,
44
70
  } from "@exponent-labs/marginfi-sy-idl"
45
- import { decodePoolAccount, decodeVaultAccount } from "@exponent-labs/meteora-idl"
71
+ import { fetchPoolAccount, fetchVaultAccount } from "@exponent-labs/meteora-idl"
46
72
  import { PROGRAM_ID as PERENA_STANDARD_PROGRAM_ID } from "@exponent-labs/perena-sy-idl"
47
73
  import { IDL as PerenaSyIdl } from "@exponent-labs/perena-sy-idl"
48
74
  import { PerenaStandard } from "@exponent-labs/perena-sy-idl"
49
75
  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
76
  import { decodeYieldPoolAndVestingScheduleAccounts } from "@exponent-labs/solstice-idl"
61
77
 
78
+ import { calculateAdrenaIndex } from "./utils/adrena"
79
+ import { calculateFragmetricIndex, calculateFragmetricSupportedTokenIndex } from "./utils/fragmetric"
80
+ import { decodeJitoVaultData } from "./utils/jito"
81
+ import { calculateJupiterLendIndex, calculateJupiterPerpsIndex } from "./utils/jupiter"
62
82
  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"
83
+ import { calculateOreExchangeRate } from "./utils/ore"
84
+ import { getPerenaLpMint, getPerenaStablePoolData } from "./utils/perena"
85
+ import { calculateSanctumIndex } from "./utils/sanctum"
86
+ import { calculateSolsticeRedemptionRate } from "./utils/solstice"
66
87
 
67
88
  export function serializeAnchorizedPNumFromJson(pnum: AnchorizedPNum): AnchorizedPNumJson {
68
89
  const serializedArray = pnum[0].map((bn) => bn.toString())
69
90
  return { 0: serializedArray }
70
91
  }
71
92
 
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
+
72
99
  export function deserializeAnchorizedPNumFromJson(serialized: AnchorizedPNumJson): AnchorizedPNum {
73
100
  const bnArray = serialized[0].map((str) => new BN(str))
74
101
  return { 0: bnArray }
@@ -111,6 +138,8 @@ export class ExponentFetcher {
111
138
  public jitoRestakingSyProgram: Program<JitoRestakingStandard>
112
139
  public perenaSyProgram: Program<PerenaStandard>
113
140
  public genericStandardProgram: Program<GenericStandard>
141
+ public exponentClmmProgram: Program<ExponentClmm>
142
+ public orderbookProgram: Program<ExponentOrderbook>
114
143
  public connection: web3.Connection
115
144
  public coreProgramId: web3.PublicKey
116
145
  public marginfiSyProgramId: web3.PublicKey
@@ -156,6 +185,8 @@ export class ExponentFetcher {
156
185
  )
157
186
  this.perenaSyProgram = new Program<PerenaStandard>(PerenaSyIdl as PerenaStandard, provider)
158
187
  this.genericStandardProgram = new Program<GenericStandard>(GenericStandardIdl, provider)
188
+ this.exponentClmmProgram = new Program<ExponentClmm>(ExponentClmmIdl as ExponentClmm, provider)
189
+ this.orderbookProgram = new Program<ExponentOrderbook>(EXPONENT_ORDERBOOK_IDL as ExponentOrderbook, provider)
159
190
  }
160
191
 
161
192
  async fetchVault(address: web3.PublicKey) {
@@ -180,6 +211,30 @@ export class ExponentFetcher {
180
211
  }
181
212
  }
182
213
 
214
+ async fetchOrderbook(address: web3.PublicKey): Promise<Orderbook> {
215
+ try {
216
+ const o = (await this.connection.getAccountInfo(address)).data
217
+
218
+ return deserializeOrderbook(o)
219
+ } catch (e) {
220
+ console.error(`Error fetching orderbook ${address.toBase58()}`)
221
+ console.error(e)
222
+ throw e
223
+ }
224
+ }
225
+
226
+ async fetchOrderbookCpiAccounts(address: web3.PublicKey): Promise<OrderbookCpiAccountsRaw> {
227
+ try {
228
+ const orderbookCpiAccounts: OrderbookCpiAccountsRaw =
229
+ await this.orderbookProgram.account.cpiAccountsOrderbook.fetch(address)
230
+ return orderbookCpiAccounts
231
+ } catch (e) {
232
+ console.error(`Error fetching orderbook ${address.toBase58()}`)
233
+ console.error(e)
234
+ throw e
235
+ }
236
+ }
237
+
183
238
  async fetchMarginfiSyMeta(address: web3.PublicKey): Promise<MarginfiSyMeta> {
184
239
  const x: MarginfiSyMetaRaw = await this.marginfiSyProgram.account.syMeta.fetch(address)
185
240
  return deserializeMarginfiSyMeta(x)
@@ -263,11 +318,62 @@ export class ExponentFetcher {
263
318
  return deserializeLpPosition(x)
264
319
  }
265
320
 
321
+ async fetchLpPositionCLMM(address: web3.PublicKey) {
322
+ try {
323
+ const raw = await this.exponentClmmProgram.account.lpPosition.fetch(address)
324
+
325
+ const v: LpPositionCLMMRaw = {
326
+ owner: raw.owner,
327
+ market: raw.market,
328
+ feeInsideLastPt: raw.feeInsideLastPt,
329
+ feeInsideLastSy: raw.feeInsideLastSy,
330
+ lpBalance: raw.lpBalance,
331
+ tokensOwedSy: raw.tokensOwedSy,
332
+ tokensOwedPt: raw.tokensOwedPt,
333
+ lowerTickIdx: raw.lowerTickIdx,
334
+ upperTickIdx: raw.upperTickIdx,
335
+ farms: {
336
+ trackers: raw.farms.trackers.map((t: any) => ({
337
+ staged: t.staged,
338
+ lastSeenIndex: t.lastSeenIndex,
339
+ })),
340
+ },
341
+ shareTrackers: {
342
+ trackers: raw.shareTrackers.trackers.map((tracker: any) => ({
343
+ tickIdx: tracker.tickIdx,
344
+ rightTickIdx: tracker.rightTickIdx,
345
+ splitEpoch: tracker.splitEpoch,
346
+ lpShare: tracker.lpShare,
347
+ emissions: {
348
+ trackers: tracker.emissions.trackers.map((e: any) => ({
349
+ staged: e.staged,
350
+ lastSeenIndex: e.lastSeenIndex,
351
+ })),
352
+ },
353
+ })),
354
+ },
355
+ }
356
+
357
+ return deserializeLpPositionCLMM(v)
358
+ } catch (e) {
359
+ console.error(`Error fetching clmm lp position ${address.toBase58()}`)
360
+ console.error(e)
361
+ throw e
362
+ }
363
+ }
364
+
266
365
  async fetchYtPosition(address: web3.PublicKey): Promise<YtPosition> {
267
366
  const x: YtPositionRaw = await this.program.account.yieldTokenPosition.fetch(address)
268
367
  return deserializeYtPosition(x)
269
368
  }
270
369
 
370
+ /** Batch fetch multiple YT positions in a single RPC call */
371
+ async fetchYtPositions(addresses: web3.PublicKey[]): Promise<(YtPosition | null)[]> {
372
+ if (addresses.length === 0) return []
373
+ const results = await this.program.account.yieldTokenPosition.fetchMultiple(addresses)
374
+ return results.map((x) => (x ? deserializeYtPosition(x as YtPositionRaw) : null))
375
+ }
376
+
271
377
  async fetchJitoRestakingSyMeta(address: web3.PublicKey): Promise<JitoRestakingSyMetaAccount> {
272
378
  const x: JitoRestakingSyMetaAccountRaw = await this.jitoRestakingSyProgram.account.syMeta.fetch(address)
273
379
 
@@ -284,6 +390,256 @@ export class ExponentFetcher {
284
390
 
285
391
  return deserializeGenericSyMetaAccountRaw(x)
286
392
  }
393
+
394
+ async fetchAllMarketThree(): Promise<MarketThree[]> {
395
+ //TODO Replace with the following code when all damaged markets are removed onchain
396
+ // const marketsProgramAccounts: ProgramAccount<MarketThreeRaw>[] =
397
+ // await this.exponentClmmProgram.account.marketThree.all()
398
+ // return marketsProgramAccounts.map(({ account }) => deserializeMarketThree(account))
399
+
400
+ const MARKET_THREE_DISCRIMINATOR = Buffer.from([242, 240, 26, 15, 148, 186, 185, 205])
401
+ const marketsProgramAccounts = await this.connection.getProgramAccounts(this.exponentClmmProgram.programId, {
402
+ filters: [
403
+ {
404
+ memcmp: {
405
+ offset: 0,
406
+ bytes: bs58.encode(MARKET_THREE_DISCRIMINATOR),
407
+ },
408
+ },
409
+ ],
410
+ })
411
+
412
+ return marketsProgramAccounts
413
+ .map(({ account, pubkey }) => {
414
+ try {
415
+ return this.exponentClmmProgram.coder.accounts.decode("marketThree", account.data)
416
+ } catch (error) {
417
+ return null
418
+ }
419
+ })
420
+ .filter((m) => !!m)
421
+ }
422
+
423
+ async fetchMarketThree(address: web3.PublicKey): Promise<MarketThree> {
424
+ try {
425
+ const m: MarketThreeRaw = await this.exponentClmmProgram.account.marketThree.fetch(address)
426
+ return deserializeMarketThree(m)
427
+ } catch (e) {
428
+ console.error(`Error fetching market ${address.toBase58()}`)
429
+ console.error(e)
430
+ throw e
431
+ }
432
+ }
433
+
434
+ async fetchAllMarketThreeTicks(): Promise<Ticks[]> {
435
+ const TICKS_DISCRIMINATOR = Buffer.from([122, 104, 41, 141, 214, 36, 222, 37])
436
+
437
+ const ticksAccounts = await this.connection.getProgramAccounts(this.exponentClmmProgram.programId, {
438
+ filters: [
439
+ {
440
+ memcmp: {
441
+ offset: 0,
442
+ bytes: bs58.encode(TICKS_DISCRIMINATOR),
443
+ },
444
+ },
445
+ ],
446
+ })
447
+
448
+ return ticksAccounts.map(({ account }) => deserializeMarketThreeTicks(account.data))
449
+ }
450
+
451
+ async fetchMarketThreeTicks(address: web3.PublicKey): Promise<Ticks> {
452
+ try {
453
+ const m = (await this.connection.getAccountInfo(address)).data
454
+ return deserializeMarketThreeTicks(m)
455
+ } catch (e) {
456
+ console.error(`Error fetching market ${address.toBase58()}`)
457
+ console.error(e)
458
+ throw e
459
+ }
460
+ }
461
+ }
462
+
463
+ export function deserializeMarketThreeTicks(data: Buffer): Ticks {
464
+ let offset = 8
465
+ const MAX_TICK_NODES = 100
466
+ const PERSONAL_TICK_YIELD_TRACKER_SIZE = 3
467
+
468
+ const readPubkey = (): web3.PublicKey => {
469
+ const pk = new web3.PublicKey(data.slice(offset, offset + 32))
470
+ offset += 32
471
+ return pk
472
+ }
473
+
474
+ const readNumber = (): number => {
475
+ // Number is 32 bytes (4 x u64)
476
+ const nums = []
477
+ for (let i = 0; i < 4; i++) {
478
+ nums.push(new BN(data.slice(offset + i * 8, offset + (i + 1) * 8), undefined, "le"))
479
+ }
480
+ offset += 32
481
+ return parseFloat(PreciseNumber.fromRaw(nums).valueString)
482
+ }
483
+
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
497
+
498
+ const ticksTreeBump = data.readUInt32LE(offset)
499
+ offset += 4
500
+
501
+ const ticksTreeFreeIdx = data.readUInt32LE(offset)
502
+ offset += 4
503
+
504
+ const ticks: Tick[] = []
505
+
506
+ 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 = []
537
+ for (let j = 0; j < PERSONAL_TICK_YIELD_TRACKER_SIZE; j++) {
538
+ farms.push({ lastSeenIndex: readNumber() })
539
+ }
540
+
541
+ // Parse EmissionYieldTrackers (3 trackers x 64 bytes each)
542
+ const emissions = []
543
+ for (let j = 0; j < PERSONAL_TICK_YIELD_TRACKER_SIZE; j++) {
544
+ const lastSeenIndex = readNumber()
545
+ const lastPositionIndex = readNumber()
546
+ emissions.push({ lastSeenIndex, lastPositionIndex })
547
+ }
548
+
549
+ // Parse last_split_epoch (u64)
550
+ const lastSplitEpoch = data.readBigUInt64LE(offset)
551
+ offset += 8
552
+
553
+ // Skip padding (u64)
554
+ offset += 8
555
+
556
+ if (apyBasePoints === 0) continue
557
+ ticks.push({
558
+ apyBasePoints,
559
+ liquidityNet,
560
+ feeGrowthOutsidePt,
561
+ feeGrowthOutsideSy,
562
+ liquidityGross,
563
+ impliedRate,
564
+ principalPt,
565
+ principalSy,
566
+ principalShareSupply,
567
+ farms,
568
+ emissions,
569
+ lastSplitEpoch,
570
+ })
571
+ // console.log(ticks)
572
+ }
573
+
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
586
+
587
+ return {
588
+ ticksTree: ticks,
589
+ market,
590
+ feeGrowthIndexGlobalPt,
591
+ feeGrowthIndexGlobalSy,
592
+ currentPrefixSum,
593
+ currentSpotPrice,
594
+ currentTick,
595
+ }
596
+ }
597
+
598
+ function deserializeMarketThree(m: MarketThreeRaw): MarketThree {
599
+ return {
600
+ addressLookupTable: m.addressLookupTable,
601
+ mintSy: m.mintSy,
602
+ mintPt: m.mintPt,
603
+ vault: m.vault,
604
+ tokenSyEscrow: m.tokenSyEscrow,
605
+ tokenPtEscrow: m.tokenPtEscrow,
606
+ tokenFeeTreasurySy: m.tokenFeeTreasurySy,
607
+ tokenFeeTreasuryPt: m.tokenFeeTreasuryPt,
608
+ selfAddress: m.selfAddress,
609
+ syProgram: m.syProgram,
610
+ statusFlags: m.statusFlags,
611
+ cpiSyAccounts: m.cpiSyAccounts,
612
+ isCurrentFlashSwap: m.isCurrentFlashSwap,
613
+ lpFarm: m.lpFarm,
614
+ mintYt: m.mintYt,
615
+ tokenYtEscrow: m.tokenYtEscrow,
616
+ emissions: {
617
+ trackers: m.emissions.trackers.map((t) => ({
618
+ tokenEscrow: t.tokenEscrow,
619
+ lpShareIndex: deserializeAnchorizedPNum(t.lpShareIndex),
620
+ lastSeenStaged: Number(t.lastSeenStaged),
621
+ })),
622
+ },
623
+ liquidityNetBalanceLimits: m.liquidityNetBalanceLimits,
624
+ admin: m.admin,
625
+ ticks: m.ticks,
626
+ configurationOptions: {
627
+ lnFeeRateRoot: m.configurationOptions.lnFeeRateRoot,
628
+ treasuryFeeBps: m.configurationOptions.treasuryFeeBps,
629
+ minLpTickAmount: BigInt(m.configurationOptions.minLpTickAmount.toString()),
630
+ epsilonClamp: m.configurationOptions.epsilonClamp,
631
+ maxLpSupply: BigInt(m.configurationOptions.maxLpSupply.toString()),
632
+ tickSpace: m.configurationOptions.tickSpace,
633
+ },
634
+ financials: {
635
+ expirationTs: BigInt(m.financials.expirationTs),
636
+ ptBalance: BigInt(m.financials.ptBalance.toString()),
637
+ syBalance: BigInt(m.financials.syBalance.toString()),
638
+ liquidityBalance: BigInt(m.financials.liquidityBalance.toString()),
639
+ },
640
+ cpiCoreAccounts: m.cpiCoreAccounts,
641
+ exponentCoreProgram: m.exponentCoreProgram,
642
+ }
287
643
  }
288
644
 
289
645
  function deserializeMarketTwo(m: MarketTwoRaw): MarketTwo {
@@ -324,6 +680,34 @@ function deserializeMarketTwo(m: MarketTwoRaw): MarketTwo {
324
680
  }
325
681
  }
326
682
 
683
+ function deserializeLpPositionCLMM(x: LpPositionCLMMRaw): LpPositionCLMM {
684
+ return {
685
+ owner: x.owner,
686
+ market: x.market,
687
+ feeInsideLastPt: BigInt(x.feeInsideLastPt.toString()),
688
+ feeInsideLastSy: BigInt(x.feeInsideLastSy.toString()),
689
+ lpBalance: BigInt(x.lpBalance.toString()),
690
+ tokensOwedSy: BigInt(x.tokensOwedSy.toString()),
691
+ tokensOwedPt: BigInt(x.tokensOwedPt.toString()),
692
+ lowerTickIdx: x.lowerTickIdx,
693
+ upperTickIdx: x.upperTickIdx,
694
+ farms: x.farms.trackers.map((t) => ({
695
+ staged: BigInt(t.staged.toString()),
696
+ lastSeenIndex: parseFloat(PreciseNumber.fromRaw(t.lastSeenIndex[0]).valueString),
697
+ })),
698
+ shareTrackers: x.shareTrackers.trackers.map((tracker) => ({
699
+ tickIdx: tracker.tickIdx,
700
+ rightTickIdx: tracker.rightTickIdx,
701
+ splitEpoch: BigInt(tracker.splitEpoch.toString()),
702
+ lpShare: BigInt(tracker.lpShare.toString()),
703
+ emissions: tracker.emissions.trackers.map((e) => ({
704
+ staged: BigInt(e.staged.toString()),
705
+ lastSeenIndex: parseFloat(PreciseNumber.fromRaw(e.lastSeenIndex[0]).valueString),
706
+ })),
707
+ })),
708
+ }
709
+ }
710
+
327
711
  function deserializeVault(x: VaultRaw): Vault {
328
712
  return {
329
713
  syProgram: x.syProgram,
@@ -355,6 +739,246 @@ function deserializeVault(x: VaultRaw): Vault {
355
739
  }
356
740
  }
357
741
 
742
+ function deserializeOrderbook(data: Buffer): Orderbook {
743
+ let offset = 0
744
+
745
+ // 1) Skip Anchor discriminator
746
+ offset += DISCRIMINATOR_SIZE
747
+
748
+ const readPubkey = (): web3.PublicKey => {
749
+ const pk = new web3.PublicKey(data.slice(offset, offset + 32))
750
+ offset += 32
751
+ return pk
752
+ }
753
+
754
+ // ConfigurationOptions struct
755
+ const thresholdAmount = data.readBigUInt64LE(offset)
756
+ offset += 8
757
+ const lnMakerFeeRate = data.readDoubleLE(offset)
758
+ offset += 8
759
+ const lnTakerFeeRate = data.readDoubleLE(offset)
760
+ offset += 8
761
+ const priceDecimals = data.readUint8(offset)
762
+ offset += 1
763
+ // Skip ConfigurationOptions padding: _placeholder_one[15] + _placeholder_two[32] + _placeholder_three[32] + _placeholder_four[32] = 111 bytes
764
+ offset += 111
765
+
766
+ // Pubkeys
767
+ const vault = readPubkey()
768
+ const yieldPosition = readPubkey()
769
+ const addressLookupTable = readPubkey()
770
+ const exponentCoreProgram = readPubkey()
771
+ const syProgram = readPubkey()
772
+ const tokenEscrowSy = readPubkey()
773
+ const tokenEscrowYt = readPubkey()
774
+ const tokenEscrowPt = readPubkey()
775
+ const cpiAccountOrderbook = readPubkey()
776
+ const admin = readPubkey()
777
+
778
+ // Skip last_sy_exchange_rate (Number type = 32 bytes)
779
+ offset += 32
780
+
781
+ // OrderbookFinancials struct
782
+ // Skip last_seen_sy_index (Number type = 32 bytes)
783
+ offset += 32
784
+ const ytBalance = data.readBigUInt64LE(offset)
785
+ offset += 8
786
+ const syBalance = data.readBigUInt64LE(offset)
787
+ offset += 8
788
+ const ptBalance = data.readBigUInt64LE(offset)
789
+ offset += 8
790
+ const ytFeeBalance = data.readBigUInt64LE(offset)
791
+ offset += 8
792
+ const syFeeBalance = data.readBigUInt64LE(offset)
793
+ offset += 8
794
+ const ptFeeBalance = data.readBigUInt64LE(offset)
795
+ offset += 8
796
+ const stagedSyBalance = data.readBigUInt64LE(offset)
797
+ offset += 8
798
+ const expirationTs = data.readUInt32LE(offset)
799
+ offset += 4
800
+ // Skip financials _padding: [u8; 4]
801
+ offset += 4
802
+
803
+ const configurationOptions: ConfigurationOptions = {
804
+ priceDecimals,
805
+ thresholdAmount,
806
+ lnMakerFeeRate,
807
+ lnTakerFeeRate,
808
+ }
809
+ const financials: OrderbookFinancials = {
810
+ expirationTs,
811
+ syBalance: syBalance,
812
+ ytBalance: ytBalance,
813
+ ptBalance: ptBalance,
814
+ ytFeeBalance,
815
+ syFeeBalance,
816
+ ptFeeBalance,
817
+ stagedSy: stagedSyBalance,
818
+ }
819
+ // console.log("financials", financials)
820
+ // ─── Parse RedBlackTree slab ───────────────────────────────────────────────
821
+ // repr(C) gives: root: u32, pad to align NodeAllocator’s u64, then the NodeAllocator header
822
+
823
+ const root = data.readUInt32LE(offset)
824
+ offset += 4
825
+ const padTo8 = 8
826
+ offset += padTo8
827
+
828
+ // NodeAllocator<T=RBNode<u32,PriceNode>, N=MAX_PRICE_NODES, R=3>
829
+ // header: size:u64, bump_index:u32, free_list_head:u32
830
+
831
+ const priceTreeSize = Number(data.readBigUInt64LE(offset))
832
+ offset += 8
833
+
834
+ const _priceTreeBump = data.readUInt32LE(offset)
835
+ offset += 4
836
+
837
+ const _priceTreeFreeIdx = data.readUInt32LE(offset)
838
+ offset += 4
839
+
840
+ // each RBNode entry = registers[3] + key:u32 + first_offer:u32
841
+ const prices: PriceTreeNode[] = []
842
+ for (let i = 0; i < MAX_PRICE_NODES; i++) {
843
+ const left = data.readUInt32LE(offset)
844
+ offset += 4
845
+ const right = data.readUInt32LE(offset)
846
+ offset += 4
847
+ const parent = data.readUInt32LE(offset)
848
+ offset += 4
849
+ offset += 4 // skip color
850
+ const key = data.readUInt32LE(offset)
851
+ offset += 4
852
+
853
+ const firstOfferSellYt = data.readUInt32LE(offset)
854
+ offset += 4
855
+ const firstOfferBuyYt = data.readUInt32LE(offset)
856
+ offset += 4
857
+ const lastOfferSellYt = data.readUInt32LE(offset)
858
+ offset += 4
859
+ const lastOfferBuyYt = data.readUInt32LE(offset)
860
+ offset += 4
861
+ if (key === 0) continue
862
+ prices.push({ key, firstOfferSellYt, firstOfferBuyYt, lastOfferSellYt, lastOfferBuyYt, parent, left, right })
863
+ }
864
+
865
+ // ─── Parse Offers slab ────────────────────────────────────────────────────
866
+ // NodeAllocator<Offer, MAX_OFFERS, 4>
867
+ // header: size:u64, bump_index:u32, free_list_head:u32
868
+
869
+ const offersSize = Number(data.readBigUInt64LE(offset))
870
+ offset += 8
871
+ const _offersBump = data.readUInt32LE(offset)
872
+ offset += 4
873
+ const _offersFreeIdx = data.readUInt32LE(offset)
874
+ offset += 4
875
+
876
+ const offers: OfferNode[] = []
877
+ for (let i = 0; i < MAX_OFFERS; i++) {
878
+ const register = data.readUInt32LE(offset)
879
+ offset += 4
880
+ const nextOfferPointer = data.readUInt32LE(offset)
881
+ offset += 4
882
+ const userVaultPointer = data.readUInt32LE(offset)
883
+ offset += 4
884
+ const pricePointer = data.readUInt32LE(offset)
885
+ offset += 4
886
+ const amount = data.readBigUInt64LE(offset)
887
+ offset += 8
888
+ const expiryAt = data.readUInt32LE(offset)
889
+ offset += 4
890
+ const createdAt = data.readUInt32LE(offset)
891
+ offset += 4
892
+ const virtualOffer = data.readUInt8(offset) !== 0
893
+ offset += 1
894
+ const orderTypeFlag = data.readUInt8(offset)
895
+ offset += 1
896
+ const fillOrKill = data.readUInt8(offset) !== 0
897
+ offset += 1
898
+ offset += 5 // reserved padding
899
+ if (userVaultPointer === 0) continue
900
+ offers.push({
901
+ nextOfferPointer,
902
+ amount,
903
+ userVaultPointer,
904
+ expiryAt,
905
+ createdAt,
906
+ virtualOffer,
907
+ orderTypeFlag,
908
+ fillOrKill,
909
+ pricePointer,
910
+ })
911
+ }
912
+
913
+ // ─── Parse UserEscrow slab ────────────────────────────────────────────────
914
+ // NodeAllocator<UserEscrow, MAX_USER_ESCROWS, 2>
915
+ // header: size:u64, bump_index:u32, free_list_head:u32
916
+ const escSize = Number(data.readBigUInt64LE(offset))
917
+ offset += 8
918
+ const _escBump = data.readUInt32LE(offset)
919
+ offset += 4
920
+ const _escFreeIdx = data.readUInt32LE(offset)
921
+ offset += 4
922
+
923
+ // each Node = [ no registers ] + UserEscrow.value
924
+ const userEscrows: UserEscrowNode[] = []
925
+ for (let i = 0; i < MAX_USER_ESCROWS; i++) {
926
+ const register = data.readUInt32LE(offset)
927
+ offset += 4
928
+ const register2 = data.readUInt32LE(offset)
929
+ offset += 4
930
+ const user = new web3.PublicKey(data.slice(offset, offset + 32))
931
+ offset += 32
932
+ /*const yieldIndex = data.readBigUInt64LE(offset).toString();*/ offset += 32
933
+ const ptAmount = data.readBigUInt64LE(offset)
934
+ offset += 8
935
+ const syAmount = data.readBigUInt64LE(offset)
936
+ offset += 8
937
+ const ytAmount = data.readBigUInt64LE(offset)
938
+ offset += 8
939
+ const stakedYtAmount = data.readBigInt64LE(offset)
940
+ offset += 8
941
+ const staged = data.readBigInt64LE(offset)
942
+ offset += 8
943
+ offset += 8 // reserved
944
+ if (user.toBase58() == "11111111111111111111111111111111") continue
945
+ userEscrows.push({ user, yieldIndex: 0, ptAmount, syAmount, ytAmount, stakedYtAmount, staged })
946
+ }
947
+
948
+ // ─── Finally, seed_id + signer_bump + reserved ─────────────────────────────
949
+ // seed_id: [u8; 4]
950
+ const seedId = [
951
+ data.readUInt8(offset),
952
+ data.readUInt8(offset + 1),
953
+ data.readUInt8(offset + 2),
954
+ data.readUInt8(offset + 3),
955
+ ]
956
+ offset += 4
957
+ // signer_bump: [u8; 1]
958
+ const signerBump = data.readUInt8(offset)
959
+ offset += 1
960
+ // _reserved: [u8; 3] - skip
961
+ offset += 3
962
+
963
+ return {
964
+ vault,
965
+ yieldPosition,
966
+ addressLookupTable,
967
+ exponentCoreProgram,
968
+ syProgram,
969
+ admin,
970
+ tokenEscrowSy,
971
+ tokenEscrowYt,
972
+ tokenEscrowPt,
973
+ cpiAccountOrderbook,
974
+ financials,
975
+ prices,
976
+ configurationOptions,
977
+ offers,
978
+ userEscrows,
979
+ }
980
+ }
981
+
358
982
  function deserializeMarginfiSyMeta(x: MarginfiSyMetaRaw): MarginfiSyMeta {
359
983
  return {
360
984
  ...x,
@@ -386,7 +1010,7 @@ function deserializeSyEmissionRaw(x: {
386
1010
  }
387
1011
  }
388
1012
 
389
- function deserializeLpPosition(x: LpPositionRaw): LpPosition {
1013
+ export function deserializeLpPosition(x: LpPositionRaw): LpPosition {
390
1014
  return {
391
1015
  owner: x.owner,
392
1016
  market: x.market,
@@ -402,7 +1026,7 @@ function deserializeLpPosition(x: LpPositionRaw): LpPosition {
402
1026
  }
403
1027
  }
404
1028
 
405
- function deserializeYtPosition(x: YtPositionRaw): YtPosition {
1029
+ export function deserializeYtPosition(x: YtPositionRaw): YtPosition {
406
1030
  return {
407
1031
  owner: x.owner,
408
1032
  vault: x.vault,
@@ -419,16 +1043,30 @@ function deserializeYieldTokenTracker(x: YieldTokenTrackerRaw): YieldTokenTracke
419
1043
  }
420
1044
  }
421
1045
 
422
- export async function fetchKaminoReserve(address: web3.PublicKey, connection: web3.Connection) {
1046
+ export interface KaminoReserveSummary {
1047
+ lendingMarket: web3.PublicKey
1048
+ baseMint: web3.PublicKey
1049
+ assetShareValue: Decimal
1050
+ scopePriceFeed?: web3.PublicKey
1051
+ }
1052
+
1053
+ export async function fetchKaminoReserve(
1054
+ address: web3.PublicKey,
1055
+ connection: web3.Connection,
1056
+ ): Promise<KaminoReserveSummary> {
423
1057
  const reserve = await Reserve.fetch(connection, address)
424
1058
  if (!reserve) {
425
1059
  throw new Error("Reserve not found")
426
1060
  }
427
1061
 
1062
+ const rawScopePriceFeed = reserve.config.tokenInfo.scopeConfiguration.priceFeed
1063
+ const scopePriceFeed = rawScopePriceFeed.equals(web3.PublicKey.default) ? undefined : rawScopePriceFeed
1064
+
428
1065
  return {
429
1066
  lendingMarket: reserve.lendingMarket,
430
1067
  baseMint: reserve.liquidity.mintPubkey,
431
1068
  assetShareValue: reserve.getCollateralExchangeRate(),
1069
+ scopePriceFeed,
432
1070
  }
433
1071
  }
434
1072
 
@@ -492,6 +1130,157 @@ export function deserializeEmission(emission: VaultEmissionJson): VaultEmission
492
1130
  }
493
1131
  }
494
1132
 
1133
+ export interface MarketThree {
1134
+ admin: web3.PublicKey
1135
+ addressLookupTable: web3.PublicKey
1136
+ mintPt: web3.PublicKey
1137
+ mintSy: web3.PublicKey
1138
+ mintYt: web3.PublicKey
1139
+ vault: web3.PublicKey
1140
+ tokenPtEscrow: web3.PublicKey
1141
+ tokenSyEscrow: web3.PublicKey
1142
+ tokenYtEscrow: web3.PublicKey
1143
+ tokenFeeTreasurySy: web3.PublicKey
1144
+ tokenFeeTreasuryPt: web3.PublicKey
1145
+ syProgram: web3.PublicKey
1146
+ exponentCoreProgram: web3.PublicKey
1147
+ selfAddress: web3.PublicKey
1148
+ ticks: web3.PublicKey
1149
+ statusFlags: number
1150
+ configurationOptions: MarketConfigurationOptions
1151
+ financials: MarketThreeFinancials
1152
+ cpiSyAccounts: CpiAccountIndexes
1153
+ cpiCoreAccounts: MarketCpiCoreIndexes
1154
+ isCurrentFlashSwap: boolean
1155
+ lpFarm: LpFarm
1156
+ emissions: {
1157
+ trackers: {
1158
+ tokenEscrow: web3.PublicKey
1159
+ lpShareIndex: number
1160
+ lastSeenStaged: number
1161
+ }[]
1162
+ }
1163
+ liquidityNetBalanceLimits: LiquidityNetBalanceLimits
1164
+ }
1165
+
1166
+ export interface Ticks {
1167
+ /** Current tick (left boundary of the active interval) */
1168
+ currentTick: number
1169
+ /** Array of ticks (simplified from RBTree for TypeScript) */
1170
+ ticksTree: Tick[]
1171
+ /** Market address this ticks account belongs to */
1172
+ market: web3.PublicKey
1173
+ /** Fee growth index global for PT */
1174
+ feeGrowthIndexGlobalPt: bigint
1175
+ /** Fee growth index global for SY */
1176
+ feeGrowthIndexGlobalSy: bigint
1177
+ /** Current prefix sum - the active liquidity at the current tick */
1178
+ currentPrefixSum: bigint
1179
+ /** Current spot price (ln implied rate) */
1180
+ currentSpotPrice: number
1181
+ }
1182
+
1183
+ export interface Tick {
1184
+ liquidityNet: bigint
1185
+ /** Total liquidity referencing this tick as a boundary */
1186
+ liquidityGross: bigint
1187
+ /** Fee growth index outside this tick for PT */
1188
+ feeGrowthOutsidePt: bigint
1189
+ /** Fee growth index outside this tick for SY */
1190
+ feeGrowthOutsideSy: bigint
1191
+ /** Principal PT accrued in the interval starting at this tick */
1192
+ impliedRate: number
1193
+ principalPt: bigint
1194
+ /** Principal SY accrued in the interval starting at this tick */
1195
+ principalSy: bigint
1196
+ apyBasePoints: number
1197
+ principalShareSupply: bigint
1198
+ /** Farm yield trackers (3 trackers) */
1199
+ farms: { lastSeenIndex: number }[]
1200
+ /** Emission yield trackers (3 trackers) */
1201
+ emissions: { lastSeenIndex: number; lastPositionIndex: number }[]
1202
+ /** Last split epoch for this tick */
1203
+ lastSplitEpoch: bigint
1204
+ }
1205
+
1206
+ export interface MarketThreeRaw {
1207
+ admin: web3.PublicKey
1208
+ addressLookupTable: web3.PublicKey
1209
+ mintPt: web3.PublicKey
1210
+ mintSy: web3.PublicKey
1211
+ mintYt: web3.PublicKey
1212
+ vault: web3.PublicKey
1213
+ tokenPtEscrow: web3.PublicKey
1214
+ tokenSyEscrow: web3.PublicKey
1215
+ tokenYtEscrow: web3.PublicKey
1216
+ tokenFeeTreasurySy: web3.PublicKey
1217
+ tokenFeeTreasuryPt: web3.PublicKey
1218
+ syProgram: web3.PublicKey
1219
+ exponentCoreProgram: web3.PublicKey
1220
+ selfAddress: web3.PublicKey
1221
+ ticks: web3.PublicKey
1222
+ statusFlags: number
1223
+ configurationOptions: MarketConfigurationOptionsRaw
1224
+ financials: MarketThreeFinancialsRaw
1225
+ cpiSyAccounts: CpiAccountIndexes
1226
+ cpiCoreAccounts: MarketCpiCoreIndexes
1227
+ isCurrentFlashSwap: boolean
1228
+ lpFarm: LpFarm
1229
+ emissions: MarketEmissions
1230
+ liquidityNetBalanceLimits: LiquidityNetBalanceLimits
1231
+ seedId: number[]
1232
+ }
1233
+
1234
+ export interface MarketConfigurationOptions {
1235
+ /** ln of fee rate root */
1236
+ lnFeeRateRoot: number
1237
+ /** Treasury fee in basis points */
1238
+ treasuryFeeBps: number
1239
+ /** Minimum LP amount per tick */
1240
+ minLpTickAmount: bigint
1241
+ /** Epsilon clamp for numerical stability */
1242
+ epsilonClamp: number
1243
+ /** Maximum LP supply */
1244
+ maxLpSupply: bigint
1245
+ /** Tick space */
1246
+ tickSpace: number
1247
+ // priceDecimals: number ??
1248
+ }
1249
+
1250
+ export interface MarketThreeFinancials {
1251
+ expirationTs: bigint
1252
+ ptBalance: bigint
1253
+ syBalance: bigint
1254
+ liquidityBalance: bigint
1255
+ }
1256
+
1257
+ export interface MarketConfigurationOptionsRaw {
1258
+ lnFeeRateRoot: number
1259
+ treasuryFeeBps: number
1260
+ minLpTickAmount: BN
1261
+ epsilonClamp: number
1262
+ maxLpSupply: BN
1263
+ tickSpace: number
1264
+ }
1265
+
1266
+ export interface OrderbookConfigurationOptionsRaw {
1267
+ thresholdAmount: BN
1268
+ lnMakerFeeRate: number
1269
+ lnTakerFeeRate: number
1270
+ priceDecimals: number
1271
+ }
1272
+
1273
+ export interface MarketThreeFinancialsRaw {
1274
+ /** Expiration timestamp */
1275
+ expirationTs: number
1276
+ /** PT balance in the market */
1277
+ ptBalance: BN
1278
+ /** SY balance in the market */
1279
+ syBalance: BN
1280
+ /** Total liquidity balance */
1281
+ liquidityBalance: BN
1282
+ }
1283
+
495
1284
  export interface MarketTwo {
496
1285
  ptBalance: bigint
497
1286
  syBalance: bigint
@@ -557,6 +1346,42 @@ export interface Vault {
557
1346
  maxPySupply: bigint
558
1347
  }
559
1348
 
1349
+ interface OrderbookFinancials {
1350
+ expirationTs: number
1351
+ ytBalance: bigint
1352
+ syBalance: bigint
1353
+ ptBalance: bigint
1354
+ ytFeeBalance: bigint
1355
+ syFeeBalance: bigint
1356
+ ptFeeBalance: bigint
1357
+ stagedSy: bigint
1358
+ }
1359
+
1360
+ export interface ConfigurationOptions {
1361
+ priceDecimals: number
1362
+ thresholdAmount: bigint
1363
+ lnMakerFeeRate: number
1364
+ lnTakerFeeRate: number
1365
+ }
1366
+
1367
+ export interface Orderbook {
1368
+ vault: web3.PublicKey
1369
+ yieldPosition: web3.PublicKey
1370
+ addressLookupTable: web3.PublicKey
1371
+ exponentCoreProgram: web3.PublicKey
1372
+ syProgram: web3.PublicKey
1373
+ tokenEscrowSy: web3.PublicKey
1374
+ tokenEscrowYt: web3.PublicKey
1375
+ tokenEscrowPt: web3.PublicKey
1376
+ cpiAccountOrderbook: web3.PublicKey
1377
+ admin: web3.PublicKey
1378
+ configurationOptions: ConfigurationOptions
1379
+ financials: OrderbookFinancials
1380
+ prices: PriceTreeNode[]
1381
+ offers: OfferNode[]
1382
+ userEscrows: UserEscrowNode[]
1383
+ }
1384
+
560
1385
  export interface KaminoSyMeta {
561
1386
  kaminoReserve: web3.PublicKey
562
1387
  kaminoObligation: web3.PublicKey
@@ -604,7 +1429,7 @@ export interface YieldTokenTracker {
604
1429
  lastSeenIndex: number
605
1430
  }
606
1431
 
607
- interface LpPositionRaw {
1432
+ export interface LpPositionRaw {
608
1433
  owner: web3.PublicKey
609
1434
  market: web3.PublicKey
610
1435
  lpBalance: BN
@@ -612,6 +1437,48 @@ interface LpPositionRaw {
612
1437
  farms: { trackers: { staged: BN; lastSeenIndex: AnchorizedPNum }[] }
613
1438
  }
614
1439
 
1440
+ interface LpPositionCLMMRaw {
1441
+ owner: web3.PublicKey
1442
+ market: web3.PublicKey
1443
+ feeInsideLastPt: BN
1444
+ feeInsideLastSy: BN
1445
+ lpBalance: BN
1446
+ tokensOwedSy: BN
1447
+ tokensOwedPt: BN
1448
+ lowerTickIdx: number
1449
+ upperTickIdx: number
1450
+ farms: { trackers: { staged: BN; lastSeenIndex: AnchorizedPNum }[] }
1451
+ shareTrackers: {
1452
+ trackers: {
1453
+ tickIdx: number
1454
+ rightTickIdx: number
1455
+ splitEpoch: BN
1456
+ lpShare: BN
1457
+ emissions: { trackers: { staged: BN; lastSeenIndex: AnchorizedPNum }[] }
1458
+ }[]
1459
+ }
1460
+ }
1461
+
1462
+ export interface LpPositionCLMM {
1463
+ owner: web3.PublicKey
1464
+ market: web3.PublicKey
1465
+ feeInsideLastPt: bigint
1466
+ feeInsideLastSy: bigint
1467
+ lpBalance: bigint
1468
+ tokensOwedSy: bigint
1469
+ tokensOwedPt: bigint
1470
+ lowerTickIdx: number
1471
+ upperTickIdx: number
1472
+ farms: { staged: bigint; lastSeenIndex: number }[]
1473
+ shareTrackers: {
1474
+ tickIdx: number
1475
+ rightTickIdx: number
1476
+ splitEpoch: bigint
1477
+ lpShare: bigint
1478
+ emissions: { staged: bigint; lastSeenIndex: number }[]
1479
+ }[]
1480
+ }
1481
+
615
1482
  export interface LpFarm {
616
1483
  lastSeenTimestamp: number
617
1484
  farmEmissions: FarmEmissionRaw[]
@@ -636,7 +1503,7 @@ export interface MarketEmissions {
636
1503
  trackers: MarketEmission[]
637
1504
  }
638
1505
 
639
- interface MarketEmission {
1506
+ export interface MarketEmission {
640
1507
  tokenEscrow: web3.PublicKey
641
1508
  lpShareIndex: AnchorizedPNum
642
1509
  lastSeenStaged: BN
@@ -719,7 +1586,7 @@ interface KaminoSyMetaRaw {
719
1586
  emissions: SyEmissionRaw[]
720
1587
  }
721
1588
 
722
- interface YtPositionRaw {
1589
+ export interface YtPositionRaw {
723
1590
  owner: web3.PublicKey
724
1591
  vault: web3.PublicKey
725
1592
  ytBalance: BN
@@ -732,6 +1599,62 @@ interface YieldTokenTrackerRaw {
732
1599
  lastSeenIndex: AnchorizedPNum
733
1600
  }
734
1601
 
1602
+ export interface OrderbookCpiAccountsRaw {
1603
+ syCpiAccounts: CpiAccountIndexes
1604
+ exponentCoreCpiAccounts: ExponentCoreCpiIndexes
1605
+ }
1606
+
1607
+ interface OrderbookRaw {
1608
+ vault: web3.PublicKey
1609
+ yieldPosition: web3.PublicKey
1610
+ addressLookupTable: web3.PublicKey
1611
+ exponentCoreProgram: web3.PublicKey
1612
+ cpiAccountOrderbook: web3.PublicKey
1613
+ tokenEscrowSy: web3.PublicKey
1614
+ tokenEscrowYt: web3.PublicKey
1615
+ tokenEscrowPt: web3.PublicKey
1616
+ nodeCount: number
1617
+ financials: OrderbookFinancialsRaw
1618
+ prices: PriceTreeNodeRaw[]
1619
+ offers: OfferNodeRaw[]
1620
+ userEscrows: UserEscrowNodeRaw[]
1621
+ }
1622
+
1623
+ interface OrderbookFinancialsRaw {
1624
+ expirationTs: number
1625
+ ytBalance: BN
1626
+ syBalance: BN
1627
+ ptBalance: BN
1628
+ }
1629
+
1630
+ export interface PriceTreeNodeRaw {
1631
+ key: number
1632
+ firstOffer: number
1633
+ parent: number
1634
+ left: number
1635
+ right: number
1636
+ }
1637
+
1638
+ export interface OfferNodeRaw {
1639
+ register: number
1640
+ amount: BN
1641
+ userVaultPointer: number
1642
+ expiryAt: number
1643
+ createdAt: number
1644
+ virtualOffer: boolean
1645
+ orderTypeFlag: number
1646
+ fillOrKill: boolean
1647
+ }
1648
+
1649
+ export interface UserEscrowNodeRaw {
1650
+ user: web3.PublicKey
1651
+ yieldIndex: BN
1652
+ ptAmount: BN
1653
+ syAmount: BN
1654
+ ytAmount: BN
1655
+ staged: number
1656
+ }
1657
+
735
1658
  function deserializeAnchorizedPNum(x: AnchorizedPNum): number {
736
1659
  return parseFloat(PreciseNumber.fromRaw(x[0]).valueString)
737
1660
  }
@@ -745,30 +1668,7 @@ async function fetchJitoVaultData({
745
1668
  vaultAddress: web3.PublicKey
746
1669
  }) {
747
1670
  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 }
1671
+ return decodeJitoVaultData(vaultAccountInfo.data)
772
1672
  }
773
1673
 
774
1674
  async function fetchJitoSolToSolExchangeRate({
@@ -810,23 +1710,16 @@ export async function fetchPerenaStablePoolData({
810
1710
  connection: web3.Connection
811
1711
  perenaStablePool: web3.PublicKey
812
1712
  }) {
813
- const [lpMint, _] = web3.PublicKey.findProgramAddressSync(
814
- [perenaStablePool.toBuffer(), Buffer.from("liquidity")],
815
- new web3.PublicKey("NUMERUNsFCP3kuNmWZuXtm1AaQCPj9uw6Guv2Ekoi5P"),
816
- )
1713
+ const lpMint = getPerenaLpMint(perenaStablePool)
817
1714
 
818
1715
  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
1716
 
827
- const exchangeRate = new Decimal(invT.toString()).div(new Decimal(lpMintDeserialized.supply.toString())).toString()
1717
+ const { lpSupply, invT, exchangeRate } = getPerenaStablePoolData({
1718
+ perenaStablePoolData: accountInfo.data,
1719
+ lpMintData: lpMintInfo.data,
1720
+ })
828
1721
 
829
- return { lpSupply: lpMintDeserialized.supply, invT, exchangeRate, lpMint }
1722
+ return { lpSupply, invT, exchangeRate, lpMint }
830
1723
  }
831
1724
 
832
1725
  /**
@@ -880,53 +1773,19 @@ export async function fetchJupiterPerpsIndex({
880
1773
  lastAumUsd: BN
881
1774
  currentIndex: AnchorizedPNum
882
1775
  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
1776
+ }): Promise<ReturnType<typeof calculateJupiterPerpsIndex>> {
893
1777
  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
1778
 
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
-
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),
1779
+ return calculateJupiterPerpsIndex(
1780
+ { pool: accountInfo.data },
1781
+ {
1782
+ lastFeeUsdResetUnixTimestamp,
1783
+ lastRealizedFeeUsd,
1784
+ lastAumUsd,
1785
+ currentIndex,
1786
+ lastRealizedFeeUsdUpdateUnixTimestamp,
928
1787
  },
929
- }
1788
+ )
930
1789
  }
931
1790
 
932
1791
  export async function fetchPyth(connection: web3.Connection): Promise<string> {
@@ -975,13 +1834,11 @@ export async function fetchFragmetricIndex({
975
1834
  connection: web3.Connection
976
1835
  fragmetricFund: web3.PublicKey
977
1836
  }) {
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)
1837
+ const fragmetricFundRaw = await connection.getAccountInfo(fragmetricFund)
982
1838
 
983
- const receiptTokenMint = new web3.PublicKey(data.receipt_token_mint)
984
- const wrappedTokenMint = new web3.PublicKey(data.wrapped_token.mint)
1839
+ const { index, receiptTokenMint, wrappedTokenMint } = calculateFragmetricIndex({
1840
+ fragmetricFund: fragmetricFundRaw.data,
1841
+ })
985
1842
 
986
1843
  return { index, receiptTokenMint, wrappedTokenMint }
987
1844
  }
@@ -992,21 +1849,24 @@ export async function fetchJupiterLendIndex({
992
1849
  }: {
993
1850
  connection: web3.Connection
994
1851
  jupiterLendAccount: web3.PublicKey
995
- }) {
1852
+ }): Promise<ReturnType<typeof calculateJupiterLendIndex> & { rateModel: web3.PublicKey }> {
996
1853
  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
1854
+
1003
1855
  const rateModel = jupiterLendAccount.equals(new web3.PublicKey("BeAqbxfrcXmzEYT2Ra62oW2MqkuFDHaCtps47Mzg6Zj3"))
1004
1856
  ? new web3.PublicKey("Acvyi9HBGmqh3Exe1N4PjBVyY8fokq2AdC6fSLqV6KSo")
1005
1857
  : new web3.PublicKey("6iHHKAK9Mqjn57CVmWe4szAPyTH8s8pniXSj6vWaKW5r")
1006
1858
 
1007
- console.log("token reserves liquidity in fetch jupiter lend index", tokenReservesLiquidity.toBase58())
1859
+ const { index, baseTokenMint, tokenReservesLiquidity, lendingSupplyPosition, rewardsRateModel } =
1860
+ calculateJupiterLendIndex({ jupiterLend: account.data })
1008
1861
 
1009
- return { index, baseTokenMint: data.mint, tokenReservesLiquidity, lendingSupplyPosition, rewardsRateModel, rateModel }
1862
+ return {
1863
+ rateModel,
1864
+ index,
1865
+ baseTokenMint,
1866
+ tokenReservesLiquidity,
1867
+ lendingSupplyPosition,
1868
+ rewardsRateModel,
1869
+ }
1010
1870
  }
1011
1871
 
1012
1872
  export async function fetchKaminoVaultIndex({
@@ -1053,18 +1913,10 @@ export async function fetchFragmetricSupportedTokenIndex({
1053
1913
  connection: web3.Connection
1054
1914
  fragmetricFund: web3.PublicKey
1055
1915
  index: number
1056
- }) {
1916
+ }): Promise<ReturnType<typeof calculateFragmetricSupportedTokenIndex>> {
1057
1917
  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
1918
 
1067
- return { index: syIndex, receiptTokenMint, wrappedTokenMint }
1919
+ return calculateFragmetricSupportedTokenIndex({ fragmetricFund: account.data }, index)
1068
1920
  }
1069
1921
 
1070
1922
  interface AccountsInterface {
@@ -1090,14 +1942,14 @@ export async function fetchMeteoraIndex({
1090
1942
  try {
1091
1943
  const VIRTUAL_PRICE_PRECISION = new BN(100_000_000)
1092
1944
 
1093
- const pool = await decodePoolAccount(connection, accounts.pool)
1945
+ const pool = await fetchPoolAccount(connection, accounts.pool)
1094
1946
 
1095
1947
  const poolMint = await getMint(connection, pool.lpMint)
1096
1948
  const poolLpSupply = new BN(poolMint.supply.toString())
1097
1949
  const poolLpDecimals = Number(poolMint.decimals.toString())
1098
1950
 
1099
- const vaultA = await decodeVaultAccount(connection, accounts.vaultA)
1100
- const vaultB = await decodeVaultAccount(connection, accounts.vaultB)
1951
+ const vaultA = await fetchVaultAccount(connection, accounts.vaultA)
1952
+ const vaultB = await fetchVaultAccount(connection, accounts.vaultB)
1101
1953
 
1102
1954
  const vaultLpMintA = await getMint(connection, vaultA.lpMint)
1103
1955
  const vaultLpMintB = await getMint(connection, vaultB.lpMint)
@@ -1155,8 +2007,6 @@ export async function fetchAdrenaIndex({
1155
2007
  index: number
1156
2008
  }> {
1157
2009
  try {
1158
- const zero = new BN(0)
1159
-
1160
2010
  // Fetch all account data in a single RPC call
1161
2011
  const accountInfos = await connection.getMultipleAccountsInfo([
1162
2012
  accounts.pool,
@@ -1170,25 +2020,17 @@ export async function fetchAdrenaIndex({
1170
2020
  throw new Error("One or more Adrena accounts not found")
1171
2021
  }
1172
2022
 
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 }
2023
+ return calculateAdrenaIndex(
2024
+ {
2025
+ pool: accountInfos[0].data,
2026
+ custody1: accountInfos[1].data,
2027
+ custody2: accountInfos[2].data,
2028
+ custody3: accountInfos[3].data,
2029
+ custody4: accountInfos[4].data,
2030
+ },
2031
+ previousTotalFees,
2032
+ currentIndex,
2033
+ )
1192
2034
  } catch (error) {
1193
2035
  throw error
1194
2036
  }
@@ -1201,26 +2043,14 @@ export async function fetchSanctumIndex({
1201
2043
  connection: web3.Connection
1202
2044
  accounts: AccountsInterface
1203
2045
  }): 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()
2046
+ const [poolStateAccountRaw, lpMintAccountRaw] = await Promise.all(
2047
+ [accounts.poolState, accounts.lpMint].map((pk) => connection.getAccountInfo(pk)),
2048
+ )
1219
2049
 
1220
- return 1 / exchangeRate // We return inverse exchange rate
1221
- } catch (error) {
1222
- throw error
1223
- }
2050
+ return calculateSanctumIndex({
2051
+ poolStateAccountData: poolStateAccountRaw.data,
2052
+ lpMintAccountData: lpMintAccountRaw.data,
2053
+ })
1224
2054
  }
1225
2055
 
1226
2056
  export async function fetchSolsticeRedemptionRate({
@@ -1231,60 +2061,85 @@ export async function fetchSolsticeRedemptionRate({
1231
2061
  connection: web3.Connection
1232
2062
  yieldPool: web3.PublicKey
1233
2063
  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])
2064
+ }): Promise<ReturnType<typeof calculateSolsticeRedemptionRate>> {
2065
+ // Fetch both accounts in a single RPC call for efficiency
2066
+ const accountInfos = await connection.getMultipleAccountsInfo([yieldPool, vestingSchedule])
1244
2067
 
1245
- if (!accountInfos[0] || !accountInfos[1]) {
1246
- throw new Error("One or more Solstice accounts not found")
1247
- }
2068
+ if (!accountInfos[0] || !accountInfos[1]) {
2069
+ throw new Error("One or more Solstice accounts not found")
2070
+ }
1248
2071
 
1249
- // Decode accounts using solstice-idl package function
1250
- const { yieldPoolAccount, vestingScheduleAccount } = decodeYieldPoolAndVestingScheduleAccounts(accountInfos)
2072
+ return calculateSolsticeRedemptionRate({ yieldPool: accountInfos[0].data, vestingSchedule: accountInfos[1].data })
2073
+ }
1251
2074
 
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())
2075
+ const REFLECT_ORACLE_LEN = 17
2076
+ const REFLECT_MAX_STALENESS_SLOTS = 150
1256
2077
 
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
- }
2078
+ export async function fetchReflectRedemptionRate({
2079
+ connection,
2080
+ oracle,
2081
+ }: {
2082
+ connection: web3.Connection
2083
+ oracle: web3.PublicKey
2084
+ }): Promise<number> {
2085
+ const accountInfo = await connection.getAccountInfo(oracle)
1273
2086
 
1274
- totalVestedAssets = poolTotalAssets - unvestedAmount
1275
- }
2087
+ if (!accountInfo) {
2088
+ throw new Error("Reflect oracle account not found")
2089
+ }
1276
2090
 
1277
- // Calculate redemption rate: (total_vested_assets + 1) / (pool_shares_supply + 1)
1278
- const redemptionRate = Number(totalVestedAssets + 1n) / Number(poolSharesSupply + 1n)
2091
+ if (accountInfo.data.length !== REFLECT_ORACLE_LEN) {
2092
+ throw new Error(`Reflect oracle account has invalid length: ${accountInfo.data.length}`)
2093
+ }
1279
2094
 
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
2095
+ const slot = Number(accountInfo.data.readBigUInt64LE(0))
2096
+ const price = Number(accountInfo.data.readBigUInt64LE(8))
2097
+ const precision = accountInfo.data.readUInt8(16)
2098
+
2099
+ const currentSlot = await connection.getSlot()
2100
+ if (slot > currentSlot) {
2101
+ throw new Error("Reflect oracle slot is ahead of the current slot")
2102
+ }
2103
+ const slotDelta = currentSlot - slot
2104
+
2105
+ if (slotDelta > REFLECT_MAX_STALENESS_SLOTS) {
2106
+ throw new Error("Reflect oracle data is stale")
2107
+ }
2108
+
2109
+ const scale = Math.pow(10, precision)
2110
+
2111
+ if (scale === 0) {
2112
+ throw new Error("Invalid oracle precision")
1289
2113
  }
2114
+
2115
+ return price / scale
2116
+ }
2117
+
2118
+ export async function fetchOreExchangeRate({
2119
+ connection,
2120
+ storeMint,
2121
+ stakeAccount,
2122
+ treasury,
2123
+ }: {
2124
+ connection: web3.Connection
2125
+ storeMint: web3.PublicKey
2126
+ stakeAccount: web3.PublicKey
2127
+ treasury: web3.PublicKey
2128
+ }): Promise<number> {
2129
+ // Fetch all accounts in parallel for efficiency
2130
+ const [storeMintInfo, stakeAccountInfo, treasuryAccountInfo] = await Promise.all([
2131
+ connection.getAccountInfo(storeMint),
2132
+ connection.getAccountInfo(stakeAccount),
2133
+ connection.getAccountInfo(treasury),
2134
+ ])
2135
+
2136
+ if (!storeMintInfo || !stakeAccountInfo || !treasuryAccountInfo) {
2137
+ throw new Error("One or more ORE accounts not found")
2138
+ }
2139
+
2140
+ return calculateOreExchangeRate({
2141
+ stakeAccount: stakeAccountInfo.data,
2142
+ treasuryAccount: treasuryAccountInfo.data,
2143
+ storeMint: storeMintInfo.data,
2144
+ })
1290
2145
  }