@exponent-labs/exponent-fetcher 0.9.11 → 0.9.12

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.
@@ -87,8 +87,6 @@ import { getPerenaLpMint, getPerenaStablePoolData } from "./utils/perena"
87
87
  import { calculateSanctumIndex } from "./utils/sanctum"
88
88
  import { calculateSolsticeRedemptionRate } from "./utils/solstice"
89
89
 
90
- const KAMINO_VAULT_GLOBAL_CONFIG = new web3.PublicKey("BKyTcUe6daNG8HbgBix2ugdRHbykG2dK9hPBBqhUyoEX")
91
-
92
90
  export function serializeAnchorizedPNumFromJson(pnum: AnchorizedPNum): AnchorizedPNumJson {
93
91
  const serializedArray = pnum[0].map((bn) => bn.toString())
94
92
  return { 0: serializedArray }
@@ -350,6 +348,11 @@ export class ExponentFetcher {
350
348
  },
351
349
  })),
352
350
  },
351
+ crossingSplit: {
352
+ crossLeftIdx: raw.crossingSplit.crossLeftIdx,
353
+ crossRightIdx: raw.crossingSplit.crossRightIdx,
354
+ lpBalanceCrossing: raw.crossingSplit.lpBalanceCrossing,
355
+ },
353
356
  }
354
357
 
355
358
  return deserializeLpPositionCLMM(v)
@@ -477,7 +480,7 @@ export class ExponentFetcher {
477
480
  * Layout matches Rust struct `Ticks` with `RedBlackTree<u32, Tick, 1000>`:
478
481
  * - Discriminator: 8 bytes
479
482
  * - 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
483
+ * - 1000 RBTree nodes, each: nodeHeader(16) + key(4) + padding(4) + Tick(416) = 440 bytes
481
484
  * - Ticks footer: market(32) + feeGrowthPt(16) + feeGrowthSy(16) + prefixSum(8) + spotPrice(8) + currentTick(4) + padding(12) = 96 bytes
482
485
  */
483
486
  export function deserializeMarketThreeTicks(data: Buffer): Ticks {
@@ -577,13 +580,13 @@ export function deserializeMarketThreeTicks(data: Buffer): Ticks {
577
580
  const principalSy = readU64() // 8 bytes
578
581
  const principalShareSupply = readPreciseNumberAsBigint() // 32 bytes - kept as bigint for arithmetic
579
582
 
580
- // FarmYieldTrackers: 2 x FarmYieldTracker(32 bytes) = 64 bytes
583
+ // FarmYieldTrackers: 3 x FarmYieldTracker(32 bytes) = 96 bytes
581
584
  const farms: { lastSeenIndex: number }[] = []
582
585
  for (let j = 0; j < PERSONAL_TICK_YIELD_TRACKER_SIZE; j++) {
583
586
  farms.push({ lastSeenIndex: readPreciseNumberAsFloat() })
584
587
  }
585
588
 
586
- // EmissionYieldTrackers: 2 x EmissionYieldTracker(64 bytes) = 128 bytes
589
+ // EmissionYieldTrackers: 3 x EmissionYieldTracker(64 bytes) = 192 bytes
587
590
  const emissions: { lastSeenIndex: number; lastPositionIndex: number }[] = []
588
591
  for (let j = 0; j < PERSONAL_TICK_YIELD_TRACKER_SIZE; j++) {
589
592
  emissions.push({
@@ -632,76 +635,7 @@ export function deserializeMarketThreeTicks(data: Buffer): Ticks {
632
635
  }
633
636
  }
634
637
 
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
638
  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
-
705
639
  return {
706
640
  addressLookupTable: m.addressLookupTable,
707
641
  mintSy: m.mintSy,
@@ -714,7 +648,7 @@ export function deserializeMarketThree(m: MarketThreeRaw): MarketThree {
714
648
  selfAddress: m.selfAddress,
715
649
  syProgram: m.syProgram,
716
650
  statusFlags: m.statusFlags,
717
- cpiSyAccounts,
651
+ cpiSyAccounts: m.cpiSyAccounts,
718
652
  isCurrentFlashSwap: m.isCurrentFlashSwap,
719
653
  lpFarm: m.lpFarm,
720
654
  mintYt: m.mintYt,
@@ -743,9 +677,8 @@ export function deserializeMarketThree(m: MarketThreeRaw): MarketThree {
743
677
  syBalance: BigInt(m.financials.syBalance.toString()),
744
678
  liquidityBalance: BigInt(m.financials.liquidityBalance.toString()),
745
679
  },
746
- cpiCoreAccounts,
680
+ cpiCoreAccounts: m.cpiCoreAccounts,
747
681
  exponentCoreProgram: m.exponentCoreProgram,
748
- seedId: m.seedId,
749
682
  }
750
683
  }
751
684
 
@@ -812,6 +745,12 @@ function deserializeLpPositionCLMM(x: LpPositionCLMMRaw): LpPositionCLMM {
812
745
  lastSeenIndex: parseFloat(PreciseNumber.fromRaw(e.lastSeenIndex[0]).valueString),
813
746
  })),
814
747
  })),
748
+ crossingSplit: {
749
+ crossLeftIdx: x.crossingSplit.crossLeftIdx,
750
+ crossRightIdx: x.crossingSplit.crossRightIdx,
751
+ lpBalanceCrossing: BigInt(x.crossingSplit.lpBalanceCrossing.toString()),
752
+ isActive: x.crossingSplit.crossLeftIdx !== 0xffffffff && x.crossingSplit.crossRightIdx !== 0xffffffff,
753
+ },
815
754
  }
816
755
  }
817
756
 
@@ -867,7 +806,8 @@ function deserializeOrderbook(data: Buffer): Orderbook {
867
806
  offset += 8
868
807
  const priceDecimals = data.readUint8(offset)
869
808
  offset += 1
870
- // Skip ConfigurationOptions padding: _placeholder_one[15] + _placeholder_two[32] + _placeholder_three[32] + _placeholder_four[32] + _reserved[1024] = 1135 bytes
809
+ // Skip ConfigurationOptions padding/reserve:
810
+ // _placeholder_one[15] + _placeholder_two[32] + _placeholder_three[32] + _placeholder_four[32] + _reserved[1024]
871
811
  offset += 1135
872
812
 
873
813
  // Pubkeys
@@ -882,14 +822,7 @@ function deserializeOrderbook(data: Buffer): Orderbook {
882
822
  const cpiAccountOrderbook = readPubkey()
883
823
  const admin = readPubkey()
884
824
 
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
- })()
825
+ // Skip last_sy_exchange_rate (Number type = 32 bytes)
893
826
  offset += 32
894
827
 
895
828
  // OrderbookFinancials struct
@@ -1045,12 +978,7 @@ function deserializeOrderbook(data: Buffer): Orderbook {
1045
978
  offset += 4
1046
979
  const user = new web3.PublicKey(data.slice(offset, offset + 32))
1047
980
  offset += 32
1048
- const yieldIndexRaw: AnchorizedPNum = [[new BN(0), new BN(0), new BN(0), new BN(0)]]
1049
- for (let word = 0; word < 4; word++) {
1050
- yieldIndexRaw[0][word] = new BN(data.subarray(offset + word * 8, offset + (word + 1) * 8), "le")
1051
- }
1052
- const yieldIndex = deserializeAnchorizedPNum(yieldIndexRaw)
1053
- offset += 32
981
+ /*const yieldIndex = data.readBigUInt64LE(offset).toString();*/ offset += 32
1054
982
  const ptAmount = data.readBigUInt64LE(offset)
1055
983
  offset += 8
1056
984
  const syAmount = data.readBigUInt64LE(offset)
@@ -1063,7 +991,7 @@ function deserializeOrderbook(data: Buffer): Orderbook {
1063
991
  offset += 8
1064
992
  offset += 8 // reserved
1065
993
  // if (user.toBase58() == "11111111111111111111111111111111") continue
1066
- userEscrows.push({ user, yieldIndex, ptAmount, syAmount, ytAmount, stakedYtAmount, staged })
994
+ userEscrows.push({ user, yieldIndex: 0, ptAmount, syAmount, ytAmount, stakedYtAmount, staged })
1067
995
  }
1068
996
 
1069
997
  // ─── Finally, seed_id + signer_bump + reserved ─────────────────────────────
@@ -1092,14 +1020,13 @@ function deserializeOrderbook(data: Buffer): Orderbook {
1092
1020
  tokenEscrowYt,
1093
1021
  tokenEscrowPt,
1094
1022
  cpiAccountOrderbook,
1095
- lastSyExchangeRate: lastSyExchangeRateRaw,
1096
1023
  financials,
1097
1024
  prices,
1098
1025
  configurationOptions,
1099
1026
  offers,
1100
1027
  userEscrows,
1101
- offersBumpIndex,
1102
1028
  offersFreeListHead,
1029
+ offersBumpIndex,
1103
1030
  }
1104
1031
  }
1105
1032
 
@@ -1285,7 +1212,6 @@ export interface MarketThree {
1285
1212
  }[]
1286
1213
  }
1287
1214
  liquidityNetBalanceLimits: LiquidityNetBalanceLimits
1288
- seedId: number[]
1289
1215
  }
1290
1216
 
1291
1217
  export interface Ticks {
@@ -1320,13 +1246,13 @@ export interface Tick {
1320
1246
  principalSy: bigint
1321
1247
  apyBasePoints: number
1322
1248
  principalShareSupply: bigint
1323
- /** Farm yield trackers (2 trackers) */
1249
+ /** Farm yield trackers (3 trackers) */
1324
1250
  farms: { lastSeenIndex: number }[]
1325
- /** Emission yield trackers (2 trackers) */
1251
+ /** Emission yield trackers (3 trackers) */
1326
1252
  emissions: { lastSeenIndex: number; lastPositionIndex: number }[]
1327
1253
  /** Last split epoch for this tick */
1328
1254
  lastSplitEpoch: bigint
1329
- /** Frozen liquidity that cannot be withdrawn */
1255
+ /** Minimum liquidity retained on ticks used by wrapper-base liquidity */
1330
1256
  frozenLiquidity: bigint
1331
1257
  }
1332
1258
 
@@ -1502,16 +1428,12 @@ export interface Orderbook {
1502
1428
  tokenEscrowPt: web3.PublicKey
1503
1429
  cpiAccountOrderbook: web3.PublicKey
1504
1430
  admin: web3.PublicKey
1505
- /** Raw 256-bit PreciseNumber (12 decimals) for last SY exchange rate */
1506
- lastSyExchangeRate: bigint
1507
1431
  configurationOptions: ConfigurationOptions
1508
1432
  financials: OrderbookFinancials
1509
1433
  prices: PriceTreeNode[]
1510
1434
  offers: OfferNode[]
1511
1435
  userEscrows: UserEscrowNode[]
1512
- /** Next offer index that will be allocated (from NodeAllocator free list) */
1513
1436
  offersFreeListHead: number
1514
- /** Bump index boundary for offers allocator */
1515
1437
  offersBumpIndex: number
1516
1438
  }
1517
1439
 
@@ -1590,6 +1512,11 @@ interface LpPositionCLMMRaw {
1590
1512
  emissions: { trackers: { staged: BN; lastSeenIndex: AnchorizedPNum }[] }
1591
1513
  }[]
1592
1514
  }
1515
+ crossingSplit: {
1516
+ crossLeftIdx: number
1517
+ crossRightIdx: number
1518
+ lpBalanceCrossing: BN
1519
+ }
1593
1520
  }
1594
1521
 
1595
1522
  export interface LpPositionCLMM {
@@ -1610,6 +1537,12 @@ export interface LpPositionCLMM {
1610
1537
  lpShare: bigint
1611
1538
  emissions: { staged: bigint; lastSeenIndex: number }[]
1612
1539
  }[]
1540
+ crossingSplit: {
1541
+ crossLeftIdx: number
1542
+ crossRightIdx: number
1543
+ lpBalanceCrossing: bigint
1544
+ isActive: boolean
1545
+ }
1613
1546
  }
1614
1547
 
1615
1548
  export interface LpFarm {
@@ -1781,7 +1714,7 @@ export interface OfferNodeRaw {
1781
1714
 
1782
1715
  export interface UserEscrowNodeRaw {
1783
1716
  user: web3.PublicKey
1784
- yieldIndex: number
1717
+ yieldIndex: BN
1785
1718
  ptAmount: BN
1786
1719
  syAmount: BN
1787
1720
  ytAmount: BN
@@ -2025,70 +1958,28 @@ export async function fetchKaminoVaultIndex({
2025
1958
 
2026
1959
  const data = coder.accounts.decode("VaultState", account.data)
2027
1960
 
2028
- const activeAllocations = data.vault_allocation_strategy
2029
- .filter((allocation) => allocation.reserve.toBase58() !== web3.PublicKey.default.toBase58())
2030
- const activeReserves = activeAllocations.map((allocation) => allocation.reserve)
1961
+ const activeReserves = data.vault_allocation_strategy
1962
+ .map((r) => r.reserve)
1963
+ .filter((reserve) => reserve.toBase58() !== web3.PublicKey.default.toBase58())
2031
1964
 
2032
- const [activeReservesData, tokenMintInfo, sharesMintInfo] = await Promise.all([
2033
- connection.getMultipleAccountsInfo(activeReserves),
2034
- connection.getAccountInfo(data.token_mint),
2035
- connection.getAccountInfo(data.shares_mint),
2036
- ])
2037
- const decodedReserves = activeReservesData.map((accountInfo, index) => {
2038
- if (!accountInfo?.data) {
2039
- throw new Error(`Missing Kamino reserve account ${activeReserves[index].toBase58()}`)
2040
- }
2041
- return Reserve.decode(accountInfo.data)
2042
- })
2043
- const collateralMintInfos = await connection.getMultipleAccountsInfo(
2044
- decodedReserves.map((reserve) => reserve.collateral.mintPubkey),
2045
- )
1965
+ console.log("activeReserves", activeReserves)
1966
+ const activeReservesData = await connection.getMultipleAccountsInfo(activeReserves)
2046
1967
 
2047
1968
  const reserves = activeReserves.map((r, i) => {
2048
- const reserveAccount = decodedReserves[i]
2049
- const allocation = activeAllocations[i]
2050
- const [lendingMarketAuthority] = web3.PublicKey.findProgramAddressSync(
2051
- [Buffer.from("lma"), reserveAccount.lendingMarket.toBuffer()],
2052
- new web3.PublicKey("KLend2g3cP87fffoy8q1mQqGKjrxjC8boSyAYavgmjD"),
2053
- )
2054
-
1969
+ console.log("r.data", activeReservesData[i].data)
2055
1970
  return {
2056
1971
  reserveAddress: r,
2057
- marketAddress: reserveAccount.lendingMarket,
2058
- ctokenVault: allocation.ctoken_vault,
2059
- lendingMarketAuthority,
2060
- pythOracle: reserveAccount.config.tokenInfo.pythConfiguration.price,
2061
- switchboardPriceOracle: reserveAccount.config.tokenInfo.switchboardConfiguration.priceAggregator,
2062
- switchboardTwapOracle: reserveAccount.config.tokenInfo.switchboardConfiguration.twapAggregator,
2063
- scopePrices: reserveAccount.config.tokenInfo.scopeConfiguration.priceFeed,
2064
- reserveLiquiditySupply: reserveAccount.liquidity.supplyVault,
2065
- reserveCollateralMint: reserveAccount.collateral.mintPubkey,
2066
- reserveCollateralTokenProgram: collateralMintInfos[i]?.owner ?? web3.PublicKey.default,
1972
+ reserve: Reserve.decode(activeReservesData[i].data).lendingMarket,
2067
1973
  }
2068
1974
  })
2069
1975
 
1976
+ console.log("reserves", reserves)
2070
1977
  const tokenVault = data.token_vault
2071
1978
  const tokenMint = data.token_mint
2072
1979
  const baseVaultAuthority = data.base_vault_authority
2073
1980
  const sharesMint = data.shares_mint
2074
- const vaultLookupTable = data.vault_lookup_table ?? web3.PublicKey.default
2075
- const tokenProgram = tokenMintInfo?.owner ?? web3.PublicKey.default
2076
- const sharesTokenProgram = sharesMintInfo?.owner ?? web3.PublicKey.default
2077
1981
 
2078
- return {
2079
- index: 1,
2080
- tokenVault,
2081
- tokenMint,
2082
- tokenProgram,
2083
- // Kamino Vault withdraw expects the singleton program global config account.
2084
- // It is not stored on VaultState, so fetch it from the known program-wide address.
2085
- globalConfig: KAMINO_VAULT_GLOBAL_CONFIG,
2086
- baseVaultAuthority,
2087
- sharesMint,
2088
- sharesTokenProgram,
2089
- vaultLookupTable,
2090
- reserves,
2091
- }
1982
+ return { index: 1, tokenVault, tokenMint, baseVaultAuthority, sharesMint, reserves }
2092
1983
  }
2093
1984
 
2094
1985
  export async function fetchFragmetricSupportedTokenIndex({
package/src/utils/ore.ts CHANGED
@@ -3,12 +3,12 @@ import { web3 } from "@coral-xyz/anchor"
3
3
  import { MintLayout } from "@solana/spl-token"
4
4
  import Decimal from "decimal.js"
5
5
 
6
- // Steel's Numeric type is a u128 with 12 decimal places of precision
7
- const NUMERIC_DECIMAL_PLACES = 12
6
+ // Steel's Numeric type is an I80F48 fixed-point number.
7
+ const NUMERIC_SCALE = new Decimal(2).pow(48)
8
8
 
9
9
  /**
10
10
  * Represents a Numeric value from the steel crate
11
- * Steel's Numeric is a u128 (16 bytes) with 1e12 scaling factor
11
+ * Steel's Numeric is a signed 128-bit I80F48 value.
12
12
  */
13
13
  export interface Numeric {
14
14
  rawValue: BN // The raw u128 value
@@ -23,7 +23,7 @@ function deserializeNumeric(data: Buffer, offset: number): Numeric {
23
23
  const low = new BN(data.slice(offset, offset + 8), "le")
24
24
  const high = new BN(data.slice(offset + 8, offset + 16), "le")
25
25
  const rawValue = low.add(high.shln(64))
26
- const decimalValue = new Decimal(rawValue.toString()).div(new Decimal(10).pow(NUMERIC_DECIMAL_PLACES))
26
+ const decimalValue = new Decimal(rawValue.toString()).div(NUMERIC_SCALE)
27
27
  return { rawValue, decimalValue }
28
28
  }
29
29
 
@@ -32,19 +32,13 @@ function deserializeNumeric(data: Buffer, offset: number): Numeric {
32
32
  * Matches the Rust struct in solana/libraries/ore_cpi/src/state/stake.rs
33
33
  */
34
34
  export interface Stake {
35
- /// The authority of this miner account.
35
+ /// The authority of this staker account.
36
36
  authority: web3.PublicKey
37
37
  /// The balance of this stake account.
38
38
  balance: BN
39
- /// Buffer a (placeholder)
40
- bufferA: BN
41
- /// Buffer b (placeholder)
42
- bufferB: BN
43
- /// Buffer c (placeholder)
44
- bufferC: BN
45
- /// Buffer d (placeholder)
46
- bufferD: BN
47
- /// The lamport reserve to pay fees for auto-compounding bots.
39
+ /// The lamport fee to pay for auto-compounding bots.
40
+ compoundFee: BN
41
+ /// The lamport reserve to pay auto-compounding fees.
48
42
  compoundFeeReserve: BN
49
43
  /// The timestamp of last claim.
50
44
  lastClaimAt: BN
@@ -58,8 +52,6 @@ export interface Stake {
58
52
  rewards: BN
59
53
  /// The total amount of ORE this staker has earned over its lifetime.
60
54
  lifetimeRewards: BN
61
- /// Buffer f (placeholder)
62
- bufferF: BN
63
55
  }
64
56
 
65
57
  /**
@@ -67,41 +59,25 @@ export interface Stake {
67
59
  * Matches the Rust struct in solana/libraries/ore_cpi/src/state/treasury.rs
68
60
  */
69
61
  export interface Treasury {
70
- /// The amount of SOL collected for buy-bury operations.
71
- balance: BN
72
- /// Buffer a (placeholder)
73
- bufferA: BN
74
- /// The amount of ORE in the motherlode rewards pool.
75
- motherlode: BN
76
- /// The cumulative ORE distributed to miners, divided by the total unclaimed ORE at the time of distribution.
77
- minerRewardsFactor: Numeric
78
62
  /// The cumulative ORE distributed to stakers, divided by the total stake at the time of distribution.
79
- stakeRewardsFactor: Numeric
80
- /// Buffer b (placeholder)
81
- bufferB: BN
82
- /// The current total amount of refined ORE mining rewards.
83
- totalRefined: BN
63
+ rewardsFactor: Numeric
84
64
  /// The current total amount of ORE staking deposits.
85
65
  totalStaked: BN
86
- /// The current total amount of unclaimed ORE mining rewards.
87
- totalUnclaimed: BN
88
66
  }
89
67
 
90
68
  /**
91
69
  * Deserialize Stake account from Buffer
92
70
  * Skips the 8-byte steel discriminator and deserializes the struct fields
93
71
  *
94
- * Account layout (152 bytes total):
72
+ * Account layout (120 bytes total):
95
73
  * - discriminator: 8 bytes
96
74
  * - authority: 32 bytes
97
75
  * - balance: 8 bytes
98
- * - buffer_a through buffer_d: 32 bytes (4 x u64)
99
- * - compound_fee_reserve: 8 bytes
76
+ * - compound_fee, compound_fee_reserve: 16 bytes (2 x u64)
100
77
  * - last_claim_at, last_deposit_at, last_withdraw_at: 24 bytes (3 x i64)
101
- * - rewards_factor: 16 bytes (u128 Numeric)
78
+ * - rewards_factor: 16 bytes (I80F48 Numeric)
102
79
  * - rewards: 8 bytes
103
80
  * - lifetime_rewards: 8 bytes
104
- * - buffer_f: 8 bytes
105
81
  */
106
82
  function deserializeStakeAccount(stakeData: Buffer): Stake {
107
83
  const DISCRIMINATOR_SIZE = 8
@@ -115,20 +91,8 @@ function deserializeStakeAccount(stakeData: Buffer): Stake {
115
91
  const balance = new BN(stakeData.slice(offset, offset + 8), "le")
116
92
  offset += 8
117
93
 
118
- // buffer_a: u64 (8 bytes)
119
- const bufferA = new BN(stakeData.slice(offset, offset + 8), "le")
120
- offset += 8
121
-
122
- // buffer_b: u64 (8 bytes)
123
- const bufferB = new BN(stakeData.slice(offset, offset + 8), "le")
124
- offset += 8
125
-
126
- // buffer_c: u64 (8 bytes)
127
- const bufferC = new BN(stakeData.slice(offset, offset + 8), "le")
128
- offset += 8
129
-
130
- // buffer_d: u64 (8 bytes)
131
- const bufferD = new BN(stakeData.slice(offset, offset + 8), "le")
94
+ // compound_fee: u64 (8 bytes)
95
+ const compoundFee = new BN(stakeData.slice(offset, offset + 8), "le")
132
96
  offset += 8
133
97
 
134
98
  // compound_fee_reserve: u64 (8 bytes)
@@ -147,7 +111,7 @@ function deserializeStakeAccount(stakeData: Buffer): Stake {
147
111
  const lastWithdrawAt = new BN(stakeData.slice(offset, offset + 8), "le")
148
112
  offset += 8
149
113
 
150
- // rewards_factor: Numeric (16 bytes = u128)
114
+ // rewards_factor: Numeric (16 bytes = I80F48)
151
115
  const rewardsFactor = deserializeNumeric(stakeData, offset)
152
116
  offset += 16
153
117
 
@@ -157,18 +121,11 @@ function deserializeStakeAccount(stakeData: Buffer): Stake {
157
121
 
158
122
  // lifetime_rewards: u64 (8 bytes)
159
123
  const lifetimeRewards = new BN(stakeData.slice(offset, offset + 8), "le")
160
- offset += 8
161
-
162
- // buffer_f: u64 (8 bytes)
163
- const bufferF = new BN(stakeData.slice(offset, offset + 8), "le")
164
124
 
165
125
  return {
166
126
  authority,
167
127
  balance,
168
- bufferA,
169
- bufferB,
170
- bufferC,
171
- bufferD,
128
+ compoundFee,
172
129
  compoundFeeReserve,
173
130
  lastClaimAt,
174
131
  lastDepositAt,
@@ -176,7 +133,6 @@ function deserializeStakeAccount(stakeData: Buffer): Stake {
176
133
  rewardsFactor,
177
134
  rewards,
178
135
  lifetimeRewards,
179
- bufferF,
180
136
  }
181
137
  }
182
138
 
@@ -184,67 +140,25 @@ function deserializeStakeAccount(stakeData: Buffer): Stake {
184
140
  * Deserialize Treasury account from Buffer
185
141
  * Skips the 8-byte steel discriminator and deserializes the struct fields
186
142
  *
187
- * Account layout (96 bytes total):
143
+ * Account layout (32 bytes total):
188
144
  * - discriminator: 8 bytes
189
- * - balance: 8 bytes
190
- * - buffer_a: 8 bytes
191
- * - motherlode: 8 bytes
192
- * - miner_rewards_factor: 16 bytes (u128 Numeric)
193
- * - stake_rewards_factor: 16 bytes (u128 Numeric)
194
- * - buffer_b: 8 bytes
195
- * - total_refined: 8 bytes
145
+ * - rewards_factor: 16 bytes (I80F48 Numeric)
196
146
  * - total_staked: 8 bytes
197
- * - total_unclaimed: 8 bytes
198
147
  */
199
148
  function deserializeTreasuryAccount(treasuryData: Buffer): Treasury {
200
149
  const DISCRIMINATOR_SIZE = 8
201
150
  let offset = DISCRIMINATOR_SIZE
202
151
 
203
- // balance: u64 (8 bytes)
204
- const balance = new BN(treasuryData.slice(offset, offset + 8), "le")
205
- offset += 8
206
-
207
- // buffer_a: u64 (8 bytes)
208
- const bufferA = new BN(treasuryData.slice(offset, offset + 8), "le")
209
- offset += 8
210
-
211
- // motherlode: u64 (8 bytes)
212
- const motherlode = new BN(treasuryData.slice(offset, offset + 8), "le")
213
- offset += 8
214
-
215
- // miner_rewards_factor: Numeric (16 bytes = u128)
216
- const minerRewardsFactor = deserializeNumeric(treasuryData, offset)
217
- offset += 16
218
-
219
- // stake_rewards_factor: Numeric (16 bytes = u128)
220
- const stakeRewardsFactor = deserializeNumeric(treasuryData, offset)
152
+ // rewards_factor: Numeric (16 bytes = I80F48)
153
+ const rewardsFactor = deserializeNumeric(treasuryData, offset)
221
154
  offset += 16
222
155
 
223
- // buffer_b: u64 (8 bytes)
224
- const bufferB = new BN(treasuryData.slice(offset, offset + 8), "le")
225
- offset += 8
226
-
227
- // total_refined: u64 (8 bytes)
228
- const totalRefined = new BN(treasuryData.slice(offset, offset + 8), "le")
229
- offset += 8
230
-
231
156
  // total_staked: u64 (8 bytes)
232
157
  const totalStaked = new BN(treasuryData.slice(offset, offset + 8), "le")
233
- offset += 8
234
-
235
- // total_unclaimed: u64 (8 bytes)
236
- const totalUnclaimed = new BN(treasuryData.slice(offset, offset + 8), "le")
237
158
 
238
159
  return {
239
- balance,
240
- bufferA,
241
- motherlode,
242
- minerRewardsFactor,
243
- stakeRewardsFactor,
244
- bufferB,
245
- totalRefined,
160
+ rewardsFactor,
246
161
  totalStaked,
247
- totalUnclaimed,
248
162
  }
249
163
  }
250
164
 
@@ -254,8 +168,8 @@ function deserializeTreasuryAccount(treasuryData: Buffer): Treasury {
254
168
  *
255
169
  * Exchange rate formula: (stake.balance + total_rewards) / stORE_supply
256
170
  *
257
- * Where total_rewards is calculated using ORE protocol's Stake::calculate_accrued_rewards:
258
- * 1. accumulated_rewards = treasury.stake_rewards_factor - stake.rewards_factor
171
+ * Where total_rewards is calculated using ORE protocol's Stake::update_rewards:
172
+ * 1. accumulated_rewards = treasury.rewards_factor - stake.rewards_factor
259
173
  * 2. personal_rewards = accumulated_rewards * stake.balance
260
174
  * 3. total_rewards = stake.rewards + personal_rewards
261
175
  *
@@ -265,8 +179,7 @@ function deserializeTreasuryAccount(treasuryData: Buffer): Treasury {
265
179
  * the exchange rate. The stake.balance is only updated via ORE program
266
180
  * deposit/withdraw instructions.
267
181
  *
268
- * Note: Numeric values are stored as fixed-point numbers with 1e12 precision.
269
- * When multiplying two Numeric values, the result is divided by 1e12 to maintain scale.
182
+ * Note: Numeric values are stored as I80F48 fixed-point numbers.
270
183
  */
271
184
  export function calculateOreExchangeRate(accounts: {
272
185
  stakeAccount: Buffer
@@ -291,22 +204,19 @@ export function calculateOreExchangeRate(accounts: {
291
204
  // This prevents exchange rate manipulation via direct token transfers
292
205
  const stakeBalance = stake.balance.toNumber()
293
206
 
294
- // Calculate accrued rewards using ORE protocol's calculate_accrued_rewards algorithm
295
- // This mirrors the exact logic from Stake::calculate_accrued_rewards() in ore_cpi
296
- const treasuryStakeRewardsFactorDecimal = treasury.stakeRewardsFactor.decimalValue
207
+ // Calculate accrued rewards using ORE protocol's update_rewards algorithm
208
+ // This mirrors the exact logic from Stake::update_rewards() in ore_cpi
209
+ const treasuryRewardsFactorDecimal = treasury.rewardsFactor.decimalValue
297
210
  const stakeRewardsFactorDecimal = stake.rewardsFactor.decimalValue
298
211
 
299
212
  let newlyAccruedRewards = 0
300
- if (treasuryStakeRewardsFactorDecimal.gt(stakeRewardsFactorDecimal)) {
301
- // accumulated_rewards = treasury.stake_rewards_factor - stake.rewards_factor
302
- const accumulatedRewards = treasuryStakeRewardsFactorDecimal.minus(stakeRewardsFactorDecimal)
213
+ if (treasuryRewardsFactorDecimal.gt(stakeRewardsFactorDecimal)) {
214
+ // accumulated_rewards = treasury.rewards_factor - stake.rewards_factor
215
+ const accumulatedRewards = treasuryRewardsFactorDecimal.minus(stakeRewardsFactorDecimal)
303
216
 
304
217
  // personal_rewards = accumulated_rewards * stake.balance
305
- // In Rust: accumulated_rewards (as Numeric) * Numeric::from_u64(stake.balance)
306
- // The Numeric type uses 1e12 scaling internally. When multiplying two Numerics,
307
- // the result is (a * b) / 1e12 to maintain scale. Then to_u64() divides by 1e12.
308
- // Since decimalValue already gives us the decimal representation (divided by 1e12),
309
- // we just multiply by balance and floor the result.
218
+ // Since decimalValue already gives us the decoded fixed-point value, we just
219
+ // multiply by balance and floor the result.
310
220
  const personalRewards = accumulatedRewards.mul(new Decimal(stakeBalance))
311
221
  newlyAccruedRewards = Math.floor(personalRewards.toNumber())
312
222
  }
@@ -318,5 +228,9 @@ export function calculateOreExchangeRate(accounts: {
318
228
  const totalBalanceWithRewards = stakeBalance + totalRewards
319
229
 
320
230
  // Calculate exchange rate: (stake.balance + accrued_rewards) / stORE_supply
231
+ if (totalBalanceWithRewards === 0) {
232
+ return 1
233
+ }
234
+
321
235
  return totalBalanceWithRewards / storeSupply
322
236
  }