@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.
- package/build/constants.d.ts +26 -0
- package/build/constants.js +58 -0
- package/build/constants.js.map +1 -0
- package/build/exponentFetcher.d.ts +32 -3
- package/build/exponentFetcher.js +255 -105
- package/build/exponentFetcher.js.map +1 -1
- package/build/index.d.ts +1 -0
- package/build/index.js +1 -0
- package/build/index.js.map +1 -1
- package/package.json +21 -20
- package/src/constants.ts +56 -0
- package/src/exponentFetcher.ts +323 -118
- package/src/index.ts +1 -0
- package/tsconfig.json +2 -1
package/src/exponentFetcher.ts
CHANGED
|
@@ -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 ${
|
|
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
|
|
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
|
|
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
|
-
|
|
465
|
-
const
|
|
466
|
-
|
|
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
|
-
|
|
475
|
-
|
|
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
|
-
|
|
485
|
-
|
|
486
|
-
|
|
487
|
-
|
|
488
|
-
|
|
489
|
-
|
|
490
|
-
|
|
491
|
-
|
|
492
|
-
|
|
493
|
-
|
|
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
|
|
499
|
-
|
|
548
|
+
const skip = (bytes: number): void => {
|
|
549
|
+
offset += bytes
|
|
550
|
+
}
|
|
500
551
|
|
|
501
|
-
|
|
502
|
-
|
|
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
|
-
|
|
508
|
-
|
|
509
|
-
|
|
510
|
-
|
|
511
|
-
const
|
|
512
|
-
|
|
513
|
-
|
|
514
|
-
|
|
515
|
-
const
|
|
516
|
-
|
|
517
|
-
|
|
518
|
-
const
|
|
519
|
-
|
|
520
|
-
const
|
|
521
|
-
|
|
522
|
-
const
|
|
523
|
-
|
|
524
|
-
|
|
525
|
-
|
|
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:
|
|
583
|
+
farms.push({ lastSeenIndex: readPreciseNumberAsFloat() })
|
|
539
584
|
}
|
|
540
585
|
|
|
541
|
-
//
|
|
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
|
-
|
|
545
|
-
|
|
546
|
-
|
|
589
|
+
emissions.push({
|
|
590
|
+
lastSeenIndex: readPreciseNumberAsFloat(),
|
|
591
|
+
lastPositionIndex: readPreciseNumberAsFloat(),
|
|
592
|
+
})
|
|
547
593
|
}
|
|
548
594
|
|
|
549
|
-
|
|
550
|
-
const
|
|
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
|
-
|
|
575
|
-
const
|
|
576
|
-
|
|
577
|
-
const feeGrowthIndexGlobalSy =
|
|
578
|
-
|
|
579
|
-
const
|
|
580
|
-
|
|
581
|
-
|
|
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
|
-
|
|
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
|
|
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
|
|
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:
|
|
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] =
|
|
764
|
-
offset +=
|
|
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
|
-
//
|
|
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
|
-
//
|
|
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
|
|
826
|
-
offset +=
|
|
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
|
|
986
|
+
const offersBumpIndex = data.readUInt32LE(offset)
|
|
872
987
|
offset += 4
|
|
873
|
-
const
|
|
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
|
-
|
|
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
|
|
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 (
|
|
1323
|
+
/** Farm yield trackers (2 trackers) */
|
|
1199
1324
|
farms: { lastSeenIndex: number }[]
|
|
1200
|
-
/** Emission yield 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:
|
|
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:
|
|
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
|
|
1885
|
-
.
|
|
1886
|
-
|
|
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
|
-
|
|
1889
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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 {
|
|
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 =
|
|
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
|
+
}
|