@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.
- package/build/client/vaults/index.d.ts +2 -0
- package/build/client/vaults/index.js +2 -0
- package/build/client/vaults/index.js.map +1 -1
- package/build/client/vaults/types/index.d.ts +2 -0
- package/build/client/vaults/types/index.js +2 -0
- package/build/client/vaults/types/index.js.map +1 -1
- package/build/client/vaults/types/kaminoFarmEntry.d.ts +15 -0
- package/build/client/vaults/types/kaminoFarmEntry.js +17 -0
- package/build/client/vaults/types/kaminoFarmEntry.js.map +1 -0
- package/build/client/vaults/types/kaminoObligationEntry.d.ts +21 -4
- package/build/client/vaults/types/kaminoObligationEntry.js +2 -1
- package/build/client/vaults/types/kaminoObligationEntry.js.map +1 -1
- package/build/client/vaults/types/positionUpdate.d.ts +9 -0
- package/build/client/vaults/types/positionUpdate.js +23 -0
- package/build/client/vaults/types/positionUpdate.js.map +1 -1
- package/build/client/vaults/types/proposalAction.js +0 -3
- package/build/client/vaults/types/proposalAction.js.map +1 -1
- package/build/client/vaults/types/reserveFarmMapping.d.ts +19 -0
- package/build/client/vaults/types/reserveFarmMapping.js +18 -0
- package/build/client/vaults/types/reserveFarmMapping.js.map +1 -0
- package/build/client/vaults/types/strategyPosition.d.ts +5 -0
- package/build/client/vaults/types/strategyPosition.js +5 -0
- package/build/client/vaults/types/strategyPosition.js.map +1 -1
- package/build/exponentVaults/aumCalculator.d.ts +25 -4
- package/build/exponentVaults/aumCalculator.js +236 -15
- package/build/exponentVaults/aumCalculator.js.map +1 -1
- package/build/exponentVaults/fetcher.d.ts +52 -0
- package/build/exponentVaults/fetcher.js +199 -0
- package/build/exponentVaults/fetcher.js.map +1 -0
- package/build/exponentVaults/index.d.ts +10 -9
- package/build/exponentVaults/index.js +26 -8
- package/build/exponentVaults/index.js.map +1 -1
- package/build/exponentVaults/kamino-farms.d.ts +144 -0
- package/build/exponentVaults/kamino-farms.js +396 -0
- package/build/exponentVaults/kamino-farms.js.map +1 -0
- package/build/exponentVaults/loopscale/client.d.ts +240 -0
- package/build/exponentVaults/loopscale/client.js +590 -0
- package/build/exponentVaults/loopscale/client.js.map +1 -0
- package/build/exponentVaults/loopscale/client.test.d.ts +1 -0
- package/build/exponentVaults/loopscale/client.test.js +183 -0
- package/build/exponentVaults/loopscale/client.test.js.map +1 -0
- package/build/exponentVaults/loopscale/helpers.d.ts +29 -0
- package/build/exponentVaults/loopscale/helpers.js +119 -0
- package/build/exponentVaults/loopscale/helpers.js.map +1 -0
- package/build/exponentVaults/loopscale/index.d.ts +3 -0
- package/build/exponentVaults/loopscale/index.js +12 -0
- package/build/exponentVaults/loopscale/index.js.map +1 -0
- package/build/exponentVaults/loopscale/prepared-transactions.d.ts +13 -0
- package/build/exponentVaults/loopscale/prepared-transactions.js +271 -0
- package/build/exponentVaults/loopscale/prepared-transactions.js.map +1 -0
- package/build/exponentVaults/loopscale/prepared-transactions.test.d.ts +1 -0
- package/build/exponentVaults/loopscale/prepared-transactions.test.js +400 -0
- package/build/exponentVaults/loopscale/prepared-transactions.test.js.map +1 -0
- package/build/exponentVaults/loopscale/prepared-types.d.ts +62 -0
- package/build/exponentVaults/loopscale/prepared-types.js +3 -0
- package/build/exponentVaults/loopscale/prepared-types.js.map +1 -0
- package/build/exponentVaults/loopscale/response-plan.d.ts +69 -0
- package/build/exponentVaults/loopscale/response-plan.js +141 -0
- package/build/exponentVaults/loopscale/response-plan.js.map +1 -0
- package/build/exponentVaults/loopscale/response-plan.test.d.ts +1 -0
- package/build/exponentVaults/loopscale/response-plan.test.js +139 -0
- package/build/exponentVaults/loopscale/response-plan.test.js.map +1 -0
- package/build/exponentVaults/loopscale/send-plan.d.ts +75 -0
- package/build/exponentVaults/loopscale/send-plan.js +235 -0
- package/build/exponentVaults/loopscale/send-plan.js.map +1 -0
- package/build/exponentVaults/loopscale/types.d.ts +443 -0
- package/build/exponentVaults/loopscale/types.js +3 -0
- package/build/exponentVaults/loopscale/types.js.map +1 -0
- package/build/exponentVaults/loopscale-client.d.ts +113 -524
- package/build/exponentVaults/loopscale-client.js +296 -539
- package/build/exponentVaults/loopscale-client.js.map +1 -1
- package/build/exponentVaults/loopscale-client.test.d.ts +1 -0
- package/build/exponentVaults/loopscale-client.test.js +162 -0
- package/build/exponentVaults/loopscale-client.test.js.map +1 -0
- package/build/exponentVaults/loopscale-client.types.d.ts +425 -0
- package/build/exponentVaults/loopscale-client.types.js +3 -0
- package/build/exponentVaults/loopscale-client.types.js.map +1 -0
- package/build/exponentVaults/loopscale-execution.d.ts +125 -0
- package/build/exponentVaults/loopscale-execution.js +341 -0
- package/build/exponentVaults/loopscale-execution.js.map +1 -0
- package/build/exponentVaults/loopscale-execution.test.d.ts +1 -0
- package/build/exponentVaults/loopscale-execution.test.js +139 -0
- package/build/exponentVaults/loopscale-execution.test.js.map +1 -0
- package/build/exponentVaults/loopscale-vault.d.ts +115 -0
- package/build/exponentVaults/loopscale-vault.js +275 -0
- package/build/exponentVaults/loopscale-vault.js.map +1 -0
- package/build/exponentVaults/loopscale-vault.test.d.ts +1 -0
- package/build/exponentVaults/loopscale-vault.test.js +102 -0
- package/build/exponentVaults/loopscale-vault.test.js.map +1 -0
- package/build/exponentVaults/policyBuilders.d.ts +62 -0
- package/build/exponentVaults/policyBuilders.js +119 -2
- package/build/exponentVaults/policyBuilders.js.map +1 -1
- package/build/exponentVaults/pricePathResolver.d.ts +45 -0
- package/build/exponentVaults/pricePathResolver.js +198 -0
- package/build/exponentVaults/pricePathResolver.js.map +1 -0
- package/build/exponentVaults/pricePathResolver.test.d.ts +1 -0
- package/build/exponentVaults/pricePathResolver.test.js +369 -0
- package/build/exponentVaults/pricePathResolver.test.js.map +1 -0
- package/build/exponentVaults/syncTransaction.js +4 -1
- package/build/exponentVaults/syncTransaction.js.map +1 -1
- package/build/exponentVaults/titan-quote.js +170 -36
- package/build/exponentVaults/titan-quote.js.map +1 -1
- package/build/exponentVaults/vault-instruction-types.d.ts +363 -0
- package/build/exponentVaults/vault-instruction-types.js +128 -0
- package/build/exponentVaults/vault-instruction-types.js.map +1 -0
- package/build/exponentVaults/vault-interaction.d.ts +203 -343
- package/build/exponentVaults/vault-interaction.js +1894 -426
- package/build/exponentVaults/vault-interaction.js.map +1 -1
- package/build/exponentVaults/vault-interaction.kamino-vault.test.d.ts +1 -0
- package/build/exponentVaults/vault-interaction.kamino-vault.test.js +143 -0
- package/build/exponentVaults/vault-interaction.kamino-vault.test.js.map +1 -0
- package/build/exponentVaults/vault.d.ts +51 -2
- package/build/exponentVaults/vault.js +324 -48
- package/build/exponentVaults/vault.js.map +1 -1
- package/build/exponentVaults/vaultTransactionBuilder.d.ts +100 -134
- package/build/exponentVaults/vaultTransactionBuilder.js +383 -285
- package/build/exponentVaults/vaultTransactionBuilder.js.map +1 -1
- package/build/exponentVaults/vaultTransactionBuilder.test.d.ts +1 -0
- package/build/exponentVaults/vaultTransactionBuilder.test.js +297 -0
- package/build/exponentVaults/vaultTransactionBuilder.test.js.map +1 -0
- package/build/marketThree.d.ts +6 -2
- package/build/marketThree.js +10 -8
- package/build/marketThree.js.map +1 -1
- package/package.json +34 -32
- package/src/client/vaults/index.ts +2 -0
- package/src/client/vaults/types/index.ts +2 -0
- package/src/client/vaults/types/kaminoFarmEntry.ts +32 -0
- package/src/client/vaults/types/kaminoObligationEntry.ts +6 -3
- package/src/client/vaults/types/positionUpdate.ts +62 -0
- package/src/client/vaults/types/proposalAction.ts +0 -3
- package/src/client/vaults/types/reserveFarmMapping.ts +35 -0
- package/src/client/vaults/types/strategyPosition.ts +18 -1
- package/src/exponentVaults/aumCalculator.ts +353 -16
- package/src/exponentVaults/fetcher.ts +257 -0
- package/src/exponentVaults/index.ts +65 -40
- package/src/exponentVaults/kamino-farms.ts +538 -0
- package/src/exponentVaults/loopscale/client.ts +808 -0
- package/src/exponentVaults/loopscale/helpers.ts +172 -0
- package/src/exponentVaults/loopscale/index.ts +57 -0
- package/src/exponentVaults/loopscale/prepared-transactions.ts +435 -0
- package/src/exponentVaults/loopscale/prepared-types.ts +73 -0
- package/src/exponentVaults/loopscale/types.ts +466 -0
- package/src/exponentVaults/policyBuilders.ts +170 -0
- package/src/exponentVaults/pricePathResolver.test.ts +466 -0
- package/src/exponentVaults/pricePathResolver.ts +273 -0
- package/src/exponentVaults/syncTransaction.ts +6 -1
- package/src/exponentVaults/titan-quote.ts +231 -45
- package/src/exponentVaults/vault-instruction-types.ts +493 -0
- package/src/exponentVaults/vault-interaction.kamino-vault.test.ts +149 -0
- package/src/exponentVaults/vault-interaction.ts +2818 -799
- package/src/exponentVaults/vault.ts +474 -63
- package/src/exponentVaults/vaultTransactionBuilder.test.ts +349 -0
- package/src/exponentVaults/vaultTransactionBuilder.ts +581 -433
- package/src/marketThree.ts +14 -6
- 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
|
-
|
|
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
|
|
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
|
|
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 +=
|
|
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:
|
|
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:
|
|
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(
|
|
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
|
-
|
|
537
|
-
|
|
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:
|
|
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:
|
|
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:
|
|
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(
|
|
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(
|
|
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
|
}
|