@exponent-labs/exponent-sdk 0.9.0 → 0.9.2

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 (155) hide show
  1. package/build/client/vaults/index.d.ts +2 -0
  2. package/build/client/vaults/index.js +2 -0
  3. package/build/client/vaults/index.js.map +1 -1
  4. package/build/client/vaults/types/index.d.ts +2 -0
  5. package/build/client/vaults/types/index.js +2 -0
  6. package/build/client/vaults/types/index.js.map +1 -1
  7. package/build/client/vaults/types/kaminoFarmEntry.d.ts +15 -0
  8. package/build/client/vaults/types/kaminoFarmEntry.js +17 -0
  9. package/build/client/vaults/types/kaminoFarmEntry.js.map +1 -0
  10. package/build/client/vaults/types/kaminoObligationEntry.d.ts +21 -4
  11. package/build/client/vaults/types/kaminoObligationEntry.js +2 -1
  12. package/build/client/vaults/types/kaminoObligationEntry.js.map +1 -1
  13. package/build/client/vaults/types/positionUpdate.d.ts +9 -0
  14. package/build/client/vaults/types/positionUpdate.js +23 -0
  15. package/build/client/vaults/types/positionUpdate.js.map +1 -1
  16. package/build/client/vaults/types/proposalAction.js +0 -3
  17. package/build/client/vaults/types/proposalAction.js.map +1 -1
  18. package/build/client/vaults/types/reserveFarmMapping.d.ts +19 -0
  19. package/build/client/vaults/types/reserveFarmMapping.js +18 -0
  20. package/build/client/vaults/types/reserveFarmMapping.js.map +1 -0
  21. package/build/client/vaults/types/strategyPosition.d.ts +5 -0
  22. package/build/client/vaults/types/strategyPosition.js +5 -0
  23. package/build/client/vaults/types/strategyPosition.js.map +1 -1
  24. package/build/exponentVaults/aumCalculator.d.ts +25 -4
  25. package/build/exponentVaults/aumCalculator.js +236 -15
  26. package/build/exponentVaults/aumCalculator.js.map +1 -1
  27. package/build/exponentVaults/fetcher.d.ts +52 -0
  28. package/build/exponentVaults/fetcher.js +199 -0
  29. package/build/exponentVaults/fetcher.js.map +1 -0
  30. package/build/exponentVaults/index.d.ts +10 -9
  31. package/build/exponentVaults/index.js +26 -8
  32. package/build/exponentVaults/index.js.map +1 -1
  33. package/build/exponentVaults/kamino-farms.d.ts +144 -0
  34. package/build/exponentVaults/kamino-farms.js +396 -0
  35. package/build/exponentVaults/kamino-farms.js.map +1 -0
  36. package/build/exponentVaults/loopscale/client.d.ts +240 -0
  37. package/build/exponentVaults/loopscale/client.js +590 -0
  38. package/build/exponentVaults/loopscale/client.js.map +1 -0
  39. package/build/exponentVaults/loopscale/client.test.d.ts +1 -0
  40. package/build/exponentVaults/loopscale/client.test.js +183 -0
  41. package/build/exponentVaults/loopscale/client.test.js.map +1 -0
  42. package/build/exponentVaults/loopscale/helpers.d.ts +29 -0
  43. package/build/exponentVaults/loopscale/helpers.js +119 -0
  44. package/build/exponentVaults/loopscale/helpers.js.map +1 -0
  45. package/build/exponentVaults/loopscale/index.d.ts +3 -0
  46. package/build/exponentVaults/loopscale/index.js +12 -0
  47. package/build/exponentVaults/loopscale/index.js.map +1 -0
  48. package/build/exponentVaults/loopscale/prepared-transactions.d.ts +13 -0
  49. package/build/exponentVaults/loopscale/prepared-transactions.js +271 -0
  50. package/build/exponentVaults/loopscale/prepared-transactions.js.map +1 -0
  51. package/build/exponentVaults/loopscale/prepared-transactions.test.d.ts +1 -0
  52. package/build/exponentVaults/loopscale/prepared-transactions.test.js +400 -0
  53. package/build/exponentVaults/loopscale/prepared-transactions.test.js.map +1 -0
  54. package/build/exponentVaults/loopscale/prepared-types.d.ts +62 -0
  55. package/build/exponentVaults/loopscale/prepared-types.js +3 -0
  56. package/build/exponentVaults/loopscale/prepared-types.js.map +1 -0
  57. package/build/exponentVaults/loopscale/response-plan.d.ts +69 -0
  58. package/build/exponentVaults/loopscale/response-plan.js +141 -0
  59. package/build/exponentVaults/loopscale/response-plan.js.map +1 -0
  60. package/build/exponentVaults/loopscale/response-plan.test.d.ts +1 -0
  61. package/build/exponentVaults/loopscale/response-plan.test.js +139 -0
  62. package/build/exponentVaults/loopscale/response-plan.test.js.map +1 -0
  63. package/build/exponentVaults/loopscale/send-plan.d.ts +75 -0
  64. package/build/exponentVaults/loopscale/send-plan.js +235 -0
  65. package/build/exponentVaults/loopscale/send-plan.js.map +1 -0
  66. package/build/exponentVaults/loopscale/types.d.ts +443 -0
  67. package/build/exponentVaults/loopscale/types.js +3 -0
  68. package/build/exponentVaults/loopscale/types.js.map +1 -0
  69. package/build/exponentVaults/loopscale-client.d.ts +113 -524
  70. package/build/exponentVaults/loopscale-client.js +296 -539
  71. package/build/exponentVaults/loopscale-client.js.map +1 -1
  72. package/build/exponentVaults/loopscale-client.test.d.ts +1 -0
  73. package/build/exponentVaults/loopscale-client.test.js +162 -0
  74. package/build/exponentVaults/loopscale-client.test.js.map +1 -0
  75. package/build/exponentVaults/loopscale-client.types.d.ts +425 -0
  76. package/build/exponentVaults/loopscale-client.types.js +3 -0
  77. package/build/exponentVaults/loopscale-client.types.js.map +1 -0
  78. package/build/exponentVaults/loopscale-execution.d.ts +125 -0
  79. package/build/exponentVaults/loopscale-execution.js +341 -0
  80. package/build/exponentVaults/loopscale-execution.js.map +1 -0
  81. package/build/exponentVaults/loopscale-execution.test.d.ts +1 -0
  82. package/build/exponentVaults/loopscale-execution.test.js +139 -0
  83. package/build/exponentVaults/loopscale-execution.test.js.map +1 -0
  84. package/build/exponentVaults/loopscale-vault.d.ts +115 -0
  85. package/build/exponentVaults/loopscale-vault.js +275 -0
  86. package/build/exponentVaults/loopscale-vault.js.map +1 -0
  87. package/build/exponentVaults/loopscale-vault.test.d.ts +1 -0
  88. package/build/exponentVaults/loopscale-vault.test.js +102 -0
  89. package/build/exponentVaults/loopscale-vault.test.js.map +1 -0
  90. package/build/exponentVaults/policyBuilders.d.ts +62 -0
  91. package/build/exponentVaults/policyBuilders.js +119 -2
  92. package/build/exponentVaults/policyBuilders.js.map +1 -1
  93. package/build/exponentVaults/pricePathResolver.d.ts +45 -0
  94. package/build/exponentVaults/pricePathResolver.js +198 -0
  95. package/build/exponentVaults/pricePathResolver.js.map +1 -0
  96. package/build/exponentVaults/pricePathResolver.test.d.ts +1 -0
  97. package/build/exponentVaults/pricePathResolver.test.js +369 -0
  98. package/build/exponentVaults/pricePathResolver.test.js.map +1 -0
  99. package/build/exponentVaults/syncTransaction.js +4 -1
  100. package/build/exponentVaults/syncTransaction.js.map +1 -1
  101. package/build/exponentVaults/titan-quote.js +170 -36
  102. package/build/exponentVaults/titan-quote.js.map +1 -1
  103. package/build/exponentVaults/vault-instruction-types.d.ts +363 -0
  104. package/build/exponentVaults/vault-instruction-types.js +128 -0
  105. package/build/exponentVaults/vault-instruction-types.js.map +1 -0
  106. package/build/exponentVaults/vault-interaction.d.ts +203 -343
  107. package/build/exponentVaults/vault-interaction.js +1894 -426
  108. package/build/exponentVaults/vault-interaction.js.map +1 -1
  109. package/build/exponentVaults/vault-interaction.kamino-vault.test.d.ts +1 -0
  110. package/build/exponentVaults/vault-interaction.kamino-vault.test.js +143 -0
  111. package/build/exponentVaults/vault-interaction.kamino-vault.test.js.map +1 -0
  112. package/build/exponentVaults/vault.d.ts +51 -2
  113. package/build/exponentVaults/vault.js +324 -48
  114. package/build/exponentVaults/vault.js.map +1 -1
  115. package/build/exponentVaults/vaultTransactionBuilder.d.ts +100 -134
  116. package/build/exponentVaults/vaultTransactionBuilder.js +383 -285
  117. package/build/exponentVaults/vaultTransactionBuilder.js.map +1 -1
  118. package/build/exponentVaults/vaultTransactionBuilder.test.d.ts +1 -0
  119. package/build/exponentVaults/vaultTransactionBuilder.test.js +297 -0
  120. package/build/exponentVaults/vaultTransactionBuilder.test.js.map +1 -0
  121. package/build/marketThree.d.ts +6 -2
  122. package/build/marketThree.js +10 -8
  123. package/build/marketThree.js.map +1 -1
  124. package/package.json +34 -32
  125. package/src/client/vaults/index.ts +2 -0
  126. package/src/client/vaults/types/index.ts +2 -0
  127. package/src/client/vaults/types/kaminoFarmEntry.ts +32 -0
  128. package/src/client/vaults/types/kaminoObligationEntry.ts +6 -3
  129. package/src/client/vaults/types/positionUpdate.ts +62 -0
  130. package/src/client/vaults/types/proposalAction.ts +0 -3
  131. package/src/client/vaults/types/reserveFarmMapping.ts +35 -0
  132. package/src/client/vaults/types/strategyPosition.ts +18 -1
  133. package/src/exponentVaults/aumCalculator.ts +353 -16
  134. package/src/exponentVaults/fetcher.ts +257 -0
  135. package/src/exponentVaults/index.ts +65 -40
  136. package/src/exponentVaults/kamino-farms.ts +538 -0
  137. package/src/exponentVaults/loopscale/client.ts +808 -0
  138. package/src/exponentVaults/loopscale/helpers.ts +172 -0
  139. package/src/exponentVaults/loopscale/index.ts +57 -0
  140. package/src/exponentVaults/loopscale/prepared-transactions.ts +435 -0
  141. package/src/exponentVaults/loopscale/prepared-types.ts +73 -0
  142. package/src/exponentVaults/loopscale/types.ts +466 -0
  143. package/src/exponentVaults/policyBuilders.ts +170 -0
  144. package/src/exponentVaults/pricePathResolver.test.ts +466 -0
  145. package/src/exponentVaults/pricePathResolver.ts +273 -0
  146. package/src/exponentVaults/syncTransaction.ts +6 -1
  147. package/src/exponentVaults/titan-quote.ts +231 -45
  148. package/src/exponentVaults/vault-instruction-types.ts +493 -0
  149. package/src/exponentVaults/vault-interaction.kamino-vault.test.ts +149 -0
  150. package/src/exponentVaults/vault-interaction.ts +2818 -799
  151. package/src/exponentVaults/vault.ts +474 -63
  152. package/src/exponentVaults/vaultTransactionBuilder.test.ts +349 -0
  153. package/src/exponentVaults/vaultTransactionBuilder.ts +581 -433
  154. package/src/marketThree.ts +14 -6
  155. package/src/exponentVaults/loopscale-client.ts +0 -1373
@@ -3,6 +3,10 @@ import {
3
3
  ClmmPositionEntry,
4
4
  clmmPositionEntryCodec,
5
5
  } from "../types/clmmPositionEntry";
6
+ import {
7
+ KaminoFarmEntry,
8
+ kaminoFarmEntryCodec,
9
+ } from "../types/kaminoFarmEntry";
6
10
  import {
7
11
  LoopscaleLoanEntry,
8
12
  loopscaleLoanEntryCodec,
@@ -34,7 +38,8 @@ export type StrategyPosition =
34
38
  | { __kind: "YieldPosition"; fields: [YieldPositionEntry] }
35
39
  | { __kind: "ClmmPosition"; fields: [ClmmPositionEntry] }
36
40
  | { __kind: "LoopscaleLoan"; fields: [LoopscaleLoanEntry] }
37
- | { __kind: "LoopscaleStrategy"; fields: [LoopscaleStrategyEntry] };
41
+ | { __kind: "LoopscaleStrategy"; fields: [LoopscaleStrategyEntry] }
42
+ | { __kind: "KaminoFarm"; fields: [KaminoFarmEntry] };
38
43
 
39
44
  export const strategyPositionCodec = getDiscriminatedUnionCodec([
40
45
  [
@@ -65,6 +70,10 @@ export const strategyPositionCodec = getDiscriminatedUnionCodec([
65
70
  "LoopscaleStrategy",
66
71
  getStructCodec([["fields", getTupleCodec([loopscaleStrategyEntryCodec])]]),
67
72
  ],
73
+ [
74
+ "KaminoFarm",
75
+ getStructCodec([["fields", getTupleCodec([kaminoFarmEntryCodec])]]),
76
+ ],
68
77
  ]);
69
78
 
70
79
  // Data Enum Helpers.
@@ -143,6 +152,14 @@ export function strategyPosition(
143
152
  "__kind",
144
153
  "LoopscaleStrategy"
145
154
  >;
155
+ export function strategyPosition(
156
+ kind: "KaminoFarm",
157
+ data: GetDiscriminatedUnionVariantContent<
158
+ StrategyPosition,
159
+ "__kind",
160
+ "KaminoFarm"
161
+ >["fields"],
162
+ ): GetDiscriminatedUnionVariant<StrategyPosition, "__kind", "KaminoFarm">;
146
163
  export function strategyPosition<K extends StrategyPosition["__kind"], Data>(
147
164
  kind: K,
148
165
  data?: Data,
@@ -16,6 +16,19 @@ import { Fraction, Obligation, Reserve } from "@exponent-labs/kamino-reserve-des
16
16
  import { Loan as LoopscaleLoan, Strategy as LoopscaleStrategy } from "@exponent-labs/loopscale-deserializer"
17
17
 
18
18
  import { Environment, LOCAL_ENV } from "../environment"
19
+ import {
20
+ KAMINO_FARM_ACCOUNT_DISCRIMINATOR_LEN,
21
+ KAMINO_FARM_GLOBAL_CONFIG_TREASURY_FEE_BPS_OFFSET,
22
+ applyKaminoFarmTreasuryFee,
23
+ calculateKaminoFarmPrincipalAmount,
24
+ decodeKaminoFarmState,
25
+ decodeKaminoFarmUserState,
26
+ decodeKaminoScopeDatedPrice,
27
+ getKaminoFarmScopePricesAddress,
28
+ projectKaminoFarmGlobalRewards,
29
+ projectKaminoFarmUserRewards,
30
+ } from "./kamino-farms"
31
+ import { getKaminoFarmsObligationFarm, KAMINO_FARMS_PROGRAM_ID } from "./../../../kamino-lend-standard/src/constants"
19
32
 
20
33
  // ============================================================================
21
34
  // Types
@@ -30,6 +43,7 @@ export enum PositionType {
30
43
  ClmmPosition = "clmmPosition",
31
44
  LoopscaleLoan = "loopscaleLoan",
32
45
  LoopscaleStrategy = "loopscaleStrategy",
46
+ KaminoFarm = "kaminoFarm",
33
47
  }
34
48
 
35
49
  export type AumResult = {
@@ -72,7 +86,11 @@ type ObligationEntryTyped = {
72
86
  lendingProgramId: PublicKey
73
87
  quotePriceId: PriceId
74
88
  reservePriceMappings: Array<{ reserve: PublicKey; reservePriceId: PriceId }>
75
- remainingAccountsAmount: bigint
89
+ reserveFarmMappings: Array<{
90
+ reserve: PublicKey
91
+ farmKind: number
92
+ farmState: PublicKey
93
+ }>
76
94
  minPriceStatusFlags: number
77
95
  }
78
96
 
@@ -91,6 +109,11 @@ type LoopscaleStrategyEntryTyped = {
91
109
  strategy: PublicKey
92
110
  }
93
111
 
112
+ type KaminoFarmEntryTyped = {
113
+ farmState: PublicKey
114
+ userState: PublicKey
115
+ }
116
+
94
117
  type TokenEntryTyped = {
95
118
  mint: PublicKey
96
119
  priceId: PriceId
@@ -98,12 +121,20 @@ type TokenEntryTyped = {
98
121
  tokenAccountVault: PublicKey
99
122
  }
100
123
 
124
+ type ValuationClock = {
125
+ currentSlot: bigint
126
+ currentUnixTimestamp: bigint
127
+ }
128
+
101
129
  // ============================================================================
102
130
  // Constants
103
131
  // ============================================================================
104
132
 
105
133
  const PRECISE_NUMBER_DECIMALS = 12
106
134
  const PRECISE_NUMBER_SCALE = new Decimal(10).pow(PRECISE_NUMBER_DECIMALS)
135
+ const DEFAULT_PUBLIC_KEY = PublicKey.default
136
+ const KAMINO_RESERVE_FARM_KIND_COLLATERAL = 0
137
+ const KAMINO_RESERVE_FARM_KIND_DEBT = 1
107
138
 
108
139
  // ============================================================================
109
140
  // AumCalculator
@@ -183,6 +214,11 @@ export class AumCalculator {
183
214
  throw new Error("Unknown PriceId variant")
184
215
  }
185
216
 
217
+ static decimalFloorToBigInt(value: Decimal | bigint | number | string): bigint {
218
+ const decimalValue = value instanceof Decimal ? value : new Decimal(value.toString())
219
+ return BigInt(decimalValue.floor().toFixed(0))
220
+ }
221
+
186
222
  static getPriceIds(priceId: PriceId): bigint[] {
187
223
  if ("simple" in priceId) {
188
224
  return [priceId.simple.priceId]
@@ -221,7 +257,7 @@ export class AumCalculator {
221
257
  }
222
258
 
223
259
  const delta = new Decimal(1).div(lastSeenRate).sub(new Decimal(1).div(currentRate))
224
- return BigInt(delta.mul(ytBalance.toString()).floor().toString())
260
+ return AumCalculator.decimalFloorToBigInt(delta.mul(ytBalance.toString()))
225
261
  }
226
262
 
227
263
  static scaleSyToCurrentRate(syAmount: bigint, finalRate: number, currentRate: number): bigint {
@@ -229,7 +265,7 @@ export class AumCalculator {
229
265
  return syAmount
230
266
  }
231
267
 
232
- return BigInt(new Decimal(syAmount.toString()).mul(finalRate).div(currentRate).floor().toString())
268
+ return AumCalculator.decimalFloorToBigInt(new Decimal(syAmount.toString()).mul(finalRate).div(currentRate))
233
269
  }
234
270
 
235
271
  private async loadMintDecimals(mint: PublicKey): Promise<number> {
@@ -248,6 +284,15 @@ export class AumCalculator {
248
284
  return cached
249
285
  }
250
286
 
287
+ private async getCurrentValuationClock(): Promise<ValuationClock> {
288
+ const currentSlot = BigInt(await this.connection.getSlot("confirmed"))
289
+ const blockTime = await this.connection.getBlockTime(Number(currentSlot))
290
+ return {
291
+ currentSlot,
292
+ currentUnixTimestamp: BigInt(blockTime ?? Math.floor(Date.now() / 1000)),
293
+ }
294
+ }
295
+
251
296
  private getPriceInputMint(priceId: PriceId, prices: ExponentPrices): PublicKey {
252
297
  const ids = AumCalculator.getPriceIds(priceId)
253
298
  const lastEntry = prices.prices[Number(ids[ids.length - 1])]
@@ -276,6 +321,12 @@ export class AumCalculator {
276
321
  return new DataView(u8.buffer, u8.byteOffset, u8.byteLength).getBigUint64(64, true)
277
322
  }
278
323
 
324
+ static parseU128LE(data: Buffer, offset: number): bigint {
325
+ const lo = data.readBigUInt64LE(offset)
326
+ const hi = data.readBigUInt64LE(offset + 8)
327
+ return lo + (hi << 64n)
328
+ }
329
+
279
330
  // ==========================================================================
280
331
  // AUM calculation per position type
281
332
  // ==========================================================================
@@ -306,7 +357,7 @@ export class AumCalculator {
306
357
  const tokenAmount = AumCalculator.parseTokenAccountAmount(Buffer.from(info.data))
307
358
  const price = AumCalculator.resolvePrice(balance.priceId, prices)
308
359
  const valueInBase = new Decimal(tokenAmount.toString()).mul(price)
309
- totalAum += BigInt(valueInBase.floor().toString())
360
+ totalAum += AumCalculator.decimalFloorToBigInt(valueInBase)
310
361
  } catch {
311
362
  // Skip if price resolution fails
312
363
  }
@@ -369,7 +420,7 @@ export class AumCalculator {
369
420
  return Array.from(aumByMint.entries()).map(([mint, aum]) => ({
370
421
  positionType: PositionType.Orderbook,
371
422
  mint,
372
- aum: BigInt(aum.floor().toString()),
423
+ aum: AumCalculator.decimalFloorToBigInt(aum),
373
424
  }))
374
425
  } catch (e) {
375
426
  console.warn("Failed to calculate orderbook AUM:", e)
@@ -486,7 +537,7 @@ export class AumCalculator {
486
537
  )
487
538
  }
488
539
 
489
- return { positionType: PositionType.YieldPosition, mint: ytMint, aum: BigInt(valueInBase.floor().toString()) }
540
+ return { positionType: PositionType.YieldPosition, mint: ytMint, aum: AumCalculator.decimalFloorToBigInt(valueInBase) }
490
541
  } catch (e) {
491
542
  console.warn("Failed to calculate yield position AUM:", e)
492
543
  return { positionType: PositionType.YieldPosition, mint: ytMint, aum: BigInt(0) }
@@ -500,7 +551,11 @@ export class AumCalculator {
500
551
  * In practice, obligation marketValueSf values are stale for our use case, so
501
552
  * we derive each side directly from the current Kamino Reserve accounts.
502
553
  */
503
- async calculateObligationAum(entry: ObligationEntryTyped, prices: ExponentPrices): Promise<AumResult> {
554
+ async calculateObligationAum(
555
+ entry: ObligationEntryTyped,
556
+ prices: ExponentPrices,
557
+ valuationClock: ValuationClock,
558
+ ): Promise<AumResult> {
504
559
  try {
505
560
  const obligation = await Obligation.fetch(this.connection, entry.obligation, entry.lendingProgramId)
506
561
  if (!obligation) {
@@ -533,8 +588,19 @@ export class AumCalculator {
533
588
 
534
589
  const allValues = [...collateralValues, ...debtValues]
535
590
  let reserveMap: Map<string, Reserve> | undefined
536
- if (allValues.length > 0) {
537
- const uniqueReserves = Array.from(new Set(allValues.map((v) => v.reserve.toBase58())))
591
+ const reserveKeysToFetch = new Set<string>()
592
+ for (const value of allValues) {
593
+ reserveKeysToFetch.add(value.reserve.toBase58())
594
+ }
595
+ for (const mapping of entry.reservePriceMappings) {
596
+ reserveKeysToFetch.add(mapping.reserve.toBase58())
597
+ }
598
+ for (const reserveFarmMapping of entry.reserveFarmMappings ?? []) {
599
+ reserveKeysToFetch.add(reserveFarmMapping.reserve.toBase58())
600
+ }
601
+
602
+ if (reserveKeysToFetch.size > 0) {
603
+ const uniqueReserves = Array.from(reserveKeysToFetch)
538
604
  const reserveKeys = uniqueReserves.map((k) => new PublicKey(k))
539
605
  const reserves = await Reserve.fetchMultiple(this.connection, reserveKeys, entry.lendingProgramId)
540
606
  reserveMap = new Map()
@@ -554,13 +620,20 @@ export class AumCalculator {
554
620
  )
555
621
  const collateralBase = await this.calculateSideInBase(collateralValues, entry, prices, reserveMap)
556
622
  const debtBase = await this.calculateSideInBase(debtValues, entry, prices, reserveMap)
623
+ const delegatedRewardBase = await this.calculateDelegatedFarmRewardAumInBase(
624
+ entry,
625
+ obligation,
626
+ prices,
627
+ reserveMap,
628
+ valuationClock,
629
+ )
557
630
 
558
- const aumDecimal = Decimal.max(collateralBase.sub(debtBase), new Decimal(0))
631
+ const aumDecimal = Decimal.max(collateralBase.sub(debtBase).add(delegatedRewardBase), new Decimal(0))
559
632
 
560
633
  return {
561
634
  positionType: PositionType.KaminoObligation,
562
635
  mint: this.state.underlyingMint.toBase58(),
563
- aum: BigInt(aumDecimal.floor().toString()),
636
+ aum: AumCalculator.decimalFloorToBigInt(aumDecimal),
564
637
  kaminoObligationExposure,
565
638
  }
566
639
  } catch (e) {
@@ -650,6 +723,154 @@ export class AumCalculator {
650
723
  return Array.from(exposureByMint.entries()).map(([mint, amount]) => ({ mint, amount }))
651
724
  }
652
725
 
726
+ private calculateKaminoFarmRewardValueInBase(params: {
727
+ farm: ReturnType<typeof decodeKaminoFarmState>
728
+ user: ReturnType<typeof decodeKaminoFarmUserState>
729
+ globalConfigData: Buffer
730
+ scopePriceData?: Buffer | null
731
+ prices: ExponentPrices
732
+ valuationClock: ValuationClock
733
+ }): Decimal {
734
+ const treasuryFeeBps = params.globalConfigData.readBigUInt64LE(
735
+ KAMINO_FARM_ACCOUNT_DISCRIMINATOR_LEN + KAMINO_FARM_GLOBAL_CONFIG_TREASURY_FEE_BPS_OFFSET,
736
+ )
737
+ const scopePrice = params.scopePriceData
738
+ ? decodeKaminoScopeDatedPrice(params.scopePriceData, params.farm.scopeOraclePriceId)
739
+ : null
740
+ const projectedRewards = projectKaminoFarmUserRewards(
741
+ params.farm,
742
+ params.user,
743
+ projectKaminoFarmGlobalRewards(params.farm, scopePrice, params.valuationClock),
744
+ )
745
+
746
+ let rewardValueInBase = new Decimal(0)
747
+ for (let rewardIndex = 0; rewardIndex < params.farm.numRewardTokens; rewardIndex += 1) {
748
+ const rewardMint = projectedRewards.projectedRewardInfos[rewardIndex].rewardMint
749
+ const rewardPriceId = this.resolveTrackedMintPriceId(rewardMint)
750
+ if (!rewardPriceId) {
751
+ throw new Error(`Missing Exponent price for Kamino farm reward mint ${rewardMint.toBase58()}`)
752
+ }
753
+
754
+ const netRewardAmount = applyKaminoFarmTreasuryFee(
755
+ projectedRewards.projectedRewardsIssuedUnclaimed[rewardIndex],
756
+ treasuryFeeBps,
757
+ )
758
+ rewardValueInBase = rewardValueInBase.add(
759
+ new Decimal(netRewardAmount.toString()).mul(AumCalculator.resolvePrice(rewardPriceId, params.prices)),
760
+ )
761
+ }
762
+
763
+ return rewardValueInBase
764
+ }
765
+
766
+ private async calculateDelegatedFarmRewardAumInBase(
767
+ entry: ObligationEntryTyped,
768
+ obligation: Obligation,
769
+ prices: ExponentPrices,
770
+ reserveMap: Map<string, Reserve> | undefined,
771
+ valuationClock: ValuationClock,
772
+ ): Promise<Decimal> {
773
+ if ((entry.reserveFarmMappings?.length ?? 0) === 0) {
774
+ return new Decimal(0)
775
+ }
776
+
777
+ const accountKeys = new Map<string, PublicKey>()
778
+ for (const reserveFarmMapping of entry.reserveFarmMappings) {
779
+ accountKeys.set(reserveFarmMapping.farmState.toBase58(), reserveFarmMapping.farmState)
780
+ const userState = getKaminoFarmsObligationFarm(
781
+ entry.obligation,
782
+ reserveFarmMapping.farmState,
783
+ KAMINO_FARMS_PROGRAM_ID,
784
+ )
785
+ accountKeys.set(userState.toBase58(), userState)
786
+ }
787
+
788
+ const uniqueAccountKeys = [...accountKeys.values()]
789
+ const accountInfos = uniqueAccountKeys.length > 0
790
+ ? await this.connection.getMultipleAccountsInfo(uniqueAccountKeys)
791
+ : []
792
+ const accountInfoMap = new Map<string, Buffer | null>()
793
+ for (let index = 0; index < uniqueAccountKeys.length; index += 1) {
794
+ accountInfoMap.set(uniqueAccountKeys[index].toBase58(), accountInfos[index]?.data ? Buffer.from(accountInfos[index]!.data) : null)
795
+ }
796
+
797
+ let totalRewardValueInBase = new Decimal(0)
798
+ for (const reserveFarmMapping of entry.reserveFarmMappings) {
799
+ let reserve = reserveMap?.get(reserveFarmMapping.reserve.toBase58())
800
+ if (!reserve) {
801
+ reserve = await Reserve.fetch(this.connection, reserveFarmMapping.reserve, entry.lendingProgramId) ?? undefined
802
+ if (reserve) {
803
+ reserveMap?.set(reserveFarmMapping.reserve.toBase58(), reserve)
804
+ }
805
+ }
806
+ if (!reserve) {
807
+ throw new Error(`Missing tracked Kamino reserve ${reserveFarmMapping.reserve.toBase58()}`)
808
+ }
809
+
810
+ const expectedFarmState = reserveFarmMapping.farmKind === KAMINO_RESERVE_FARM_KIND_COLLATERAL
811
+ ? reserve.farmCollateral
812
+ : reserveFarmMapping.farmKind === KAMINO_RESERVE_FARM_KIND_DEBT
813
+ ? reserve.farmDebt
814
+ : null
815
+ if (!expectedFarmState?.equals(reserveFarmMapping.farmState)) {
816
+ throw new Error(`Stored reserve farm mapping is stale for reserve ${reserveFarmMapping.reserve.toBase58()}`)
817
+ }
818
+
819
+ const farmData = accountInfoMap.get(reserveFarmMapping.farmState.toBase58())
820
+ if (!farmData) {
821
+ throw new Error(`Missing delegated farm account ${reserveFarmMapping.farmState.toBase58()}`)
822
+ }
823
+ const farm = decodeKaminoFarmState(farmData)
824
+ if (!farm.isDelegated) {
825
+ throw new Error(`Stored farm ${reserveFarmMapping.farmState.toBase58()} is not delegated`)
826
+ }
827
+ const userState = getKaminoFarmsObligationFarm(
828
+ entry.obligation,
829
+ reserveFarmMapping.farmState,
830
+ KAMINO_FARMS_PROGRAM_ID,
831
+ )
832
+
833
+ const additionalAccounts = [farm.globalConfig]
834
+ const scopePrices = getKaminoFarmScopePricesAddress(farm)
835
+ if (scopePrices) {
836
+ additionalAccounts.push(scopePrices)
837
+ }
838
+ const [globalConfigInfo, scopeInfo] = await this.connection.getMultipleAccountsInfo(additionalAccounts)
839
+ if (!globalConfigInfo?.data) {
840
+ throw new Error(`Missing delegated farm global config ${farm.globalConfig.toBase58()}`)
841
+ }
842
+ const globalConfigData = Buffer.from(globalConfigInfo.data)
843
+
844
+ const userData = accountInfoMap.get(userState.toBase58())
845
+ if (!userData) {
846
+ continue
847
+ }
848
+ const user = decodeKaminoFarmUserState(userData)
849
+ if (!user.delegatee.equals(entry.obligation)) {
850
+ throw new Error(`Delegated farm user ${userState.toBase58()} does not delegate to obligation`)
851
+ }
852
+ if (!user.owner.equals(obligation.owner)) {
853
+ throw new Error(`Delegated farm user ${userState.toBase58()} owner mismatch`)
854
+ }
855
+
856
+ const scopePriceData = scopePrices ? Buffer.from(scopeInfo?.data ?? []) : null
857
+ if (scopePrices && !scopeInfo?.data) {
858
+ throw new Error(`Missing delegated farm scope prices ${scopePrices.toBase58()}`)
859
+ }
860
+
861
+ totalRewardValueInBase = totalRewardValueInBase.add(this.calculateKaminoFarmRewardValueInBase({
862
+ farm,
863
+ user,
864
+ globalConfigData,
865
+ scopePriceData,
866
+ prices,
867
+ valuationClock,
868
+ }))
869
+ }
870
+
871
+ return totalRewardValueInBase
872
+ }
873
+
653
874
  /**
654
875
  * CLMM position AUM.
655
876
  * Uses MarketThree.getPtAndSyOnWithdrawLiquidity to keep parity with
@@ -694,7 +915,7 @@ export class AumCalculator {
694
915
  return {
695
916
  positionType: PositionType.ClmmPosition,
696
917
  mint: this.state.underlyingMint.toBase58(),
697
- aum: BigInt(totalValue.floor().toString()),
918
+ aum: AumCalculator.decimalFloorToBigInt(totalValue),
698
919
  }
699
920
  } catch (e) {
700
921
  console.warn("Failed to calculate CLMM position AUM:", e)
@@ -702,6 +923,76 @@ export class AumCalculator {
702
923
  }
703
924
  }
704
925
 
926
+ async calculateKaminoFarmAum(
927
+ entry: KaminoFarmEntryTyped,
928
+ prices: ExponentPrices,
929
+ valuationClock: ValuationClock,
930
+ ): Promise<AumResult> {
931
+ const zeroResult: AumResult = {
932
+ positionType: PositionType.KaminoFarm,
933
+ mint: this.state.underlyingMint.toBase58(),
934
+ aum: 0n,
935
+ }
936
+
937
+ const [farmInfo, userInfo] = await this.connection.getMultipleAccountsInfo([entry.farmState, entry.userState])
938
+ if (!farmInfo?.data || !userInfo?.data) {
939
+ return zeroResult
940
+ }
941
+
942
+ const farm = decodeKaminoFarmState(Buffer.from(farmInfo.data))
943
+ if (farm.isDelegated) {
944
+ return zeroResult
945
+ }
946
+
947
+ const user = decodeKaminoFarmUserState(Buffer.from(userInfo.data))
948
+
949
+ const principalAmount = calculateKaminoFarmPrincipalAmount(farm, user)
950
+ const principalPriceId = this.resolveTrackedMintPriceId(farm.underlyingMint)
951
+ if (!principalPriceId) {
952
+ throw new Error(`Missing Exponent price for Kamino farm underlying mint ${farm.underlyingMint.toBase58()}`)
953
+ }
954
+ const principalValueInBase = new Decimal(principalAmount.toString()).mul(
955
+ AumCalculator.resolvePrice(principalPriceId, prices),
956
+ )
957
+
958
+ if (farm.numRewardTokens === 0) {
959
+ return {
960
+ positionType: PositionType.KaminoFarm,
961
+ mint: farm.underlyingMint.toBase58(),
962
+ aum: AumCalculator.decimalFloorToBigInt(principalValueInBase),
963
+ }
964
+ }
965
+
966
+ const additionalAccounts = [farm.globalConfig]
967
+ const scopePricesAddress = getKaminoFarmScopePricesAddress(farm)
968
+ if (scopePricesAddress) {
969
+ additionalAccounts.push(scopePricesAddress)
970
+ }
971
+ const additionalInfos = await this.connection.getMultipleAccountsInfo(additionalAccounts)
972
+ const globalConfigInfo = additionalInfos[0]
973
+ if (!globalConfigInfo?.data) {
974
+ throw new Error(`Missing Kamino farm global config ${farm.globalConfig.toBase58()}`)
975
+ }
976
+ const scopeInfo = scopePricesAddress ? additionalInfos[1] : null
977
+ if (scopePricesAddress && !scopeInfo?.data) {
978
+ throw new Error(`Missing scope prices account ${scopePricesAddress.toBase58()}`)
979
+ }
980
+ const rewardValueInBase = this.calculateKaminoFarmRewardValueInBase({
981
+ farm,
982
+ user,
983
+ globalConfigData: Buffer.from(globalConfigInfo.data),
984
+ scopePriceData: scopeInfo?.data ? Buffer.from(scopeInfo.data) : null,
985
+ prices,
986
+ valuationClock,
987
+ })
988
+
989
+ return {
990
+ positionType: PositionType.KaminoFarm,
991
+ mint: farm.underlyingMint.toBase58(),
992
+ aum: AumCalculator.decimalFloorToBigInt(principalValueInBase.add(rewardValueInBase)),
993
+ }
994
+ }
995
+
705
996
  /**
706
997
  * Calculate one side (collateral or debt) of a Kamino obligation in base units.
707
998
  * For SDK-side valuation we derive the current reserve value directly from
@@ -930,7 +1221,7 @@ export class AumCalculator {
930
1221
  results.push({
931
1222
  positionType: PositionType.Reserves,
932
1223
  mint: entry.mint.toBase58(),
933
- aum: BigInt(valueInBase.floor().toString()),
1224
+ aum: AumCalculator.decimalFloorToBigInt(valueInBase),
934
1225
  })
935
1226
  } catch {
936
1227
  // Skip if price resolution fails
@@ -948,7 +1239,11 @@ export class AumCalculator {
948
1239
  * Calculate AUM for a single strategy position.
949
1240
  * Returns array of AumResults (may be multiple for tokenAccount positions).
950
1241
  */
951
- async calculatePositionAum(position: StrategyPosition, prices: ExponentPrices): Promise<AumResult[]> {
1242
+ async calculatePositionAum(
1243
+ position: StrategyPosition,
1244
+ prices: ExponentPrices,
1245
+ valuationClock?: ValuationClock,
1246
+ ): Promise<AumResult[]> {
952
1247
  if ("tokenAccount" in position) {
953
1248
  const entry = (position as { tokenAccount: { 0: TokenAccountEntryTyped } }).tokenAccount[0]
954
1249
  return this.calculateTokenAccountAum(entry, prices)
@@ -973,7 +1268,11 @@ export class AumCalculator {
973
1268
  ? ((kaminoVariant as { 0?: ObligationEntryTyped })[0] ?? undefined)
974
1269
  : (kaminoVariant as ObligationEntryTyped | undefined)
975
1270
  if (entry) {
976
- const result = await this.calculateObligationAum(entry, prices)
1271
+ const result = await this.calculateObligationAum(
1272
+ entry,
1273
+ prices,
1274
+ valuationClock ?? await this.getCurrentValuationClock(),
1275
+ )
977
1276
  return [result]
978
1277
  }
979
1278
  }
@@ -993,6 +1292,15 @@ export class AumCalculator {
993
1292
  const result = await this.calculateLoopscaleStrategyAum(entry, prices)
994
1293
  return [result]
995
1294
  }
1295
+ if ("kaminoFarm" in position) {
1296
+ const entry = (position as { kaminoFarm: { 0: KaminoFarmEntryTyped } }).kaminoFarm[0]
1297
+ const result = await this.calculateKaminoFarmAum(
1298
+ entry,
1299
+ prices,
1300
+ valuationClock ?? await this.getCurrentValuationClock(),
1301
+ )
1302
+ return [result]
1303
+ }
996
1304
  return []
997
1305
  }
998
1306
 
@@ -1000,9 +1308,10 @@ export class AumCalculator {
1000
1308
  * Calculate AUM for all positions: reserve tokens + strategy positions.
1001
1309
  */
1002
1310
  async calculatePositionsAum(prices: ExponentPrices): Promise<AumResult[]> {
1311
+ const valuationClock = await this.getCurrentValuationClock()
1003
1312
  const [reserveResults, ...positionResults] = await Promise.all([
1004
1313
  this.calculateReserveTokensAum(prices),
1005
- ...this.state.strategyPositions.map((position) => this.calculatePositionAum(position, prices)),
1314
+ ...this.state.strategyPositions.map((position) => this.calculatePositionAum(position, prices, valuationClock)),
1006
1315
  ])
1007
1316
  return [reserveResults, ...positionResults].flat()
1008
1317
  }
@@ -1010,4 +1319,32 @@ export class AumCalculator {
1010
1319
  get strategyPositions(): StrategyPosition[] {
1011
1320
  return this.state.strategyPositions
1012
1321
  }
1322
+
1323
+ private resolveTrackedMintPriceId(mint: PublicKey): PriceId | null {
1324
+ const tokenEntry = (this.state.tokenEntries as unknown as TokenEntryTyped[]).find((entry) => entry.mint.equals(mint))
1325
+ if (tokenEntry) {
1326
+ return tokenEntry.priceId
1327
+ }
1328
+
1329
+ for (const position of this.state.strategyPositions) {
1330
+ if ("tokenAccount" in position) {
1331
+ const entry = (position as { tokenAccount: { 0: TokenAccountEntryTyped } }).tokenAccount[0]
1332
+ if (entry.tokenMint.equals(mint) && entry.balances.length > 0) {
1333
+ return entry.balances[0].priceId
1334
+ }
1335
+ }
1336
+ }
1337
+
1338
+ return null
1339
+ }
1340
+
1341
+ private static convertKaminoStakeToAmount(stake: bigint, totalStake: bigint, totalAmount: bigint): bigint {
1342
+ if (stake === 0n) {
1343
+ return 0n
1344
+ }
1345
+ if (totalStake === 0n) {
1346
+ return totalAmount
1347
+ }
1348
+ return (stake * totalAmount) / totalStake
1349
+ }
1013
1350
  }