@kamino-finance/klend-sdk 7.0.2 → 7.0.4
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/dist/classes/manager.d.ts +12 -9
- package/dist/classes/manager.d.ts.map +1 -1
- package/dist/classes/manager.js +13 -47
- package/dist/classes/manager.js.map +1 -1
- package/dist/classes/market.d.ts +9 -7
- package/dist/classes/market.d.ts.map +1 -1
- package/dist/classes/market.js +49 -16
- package/dist/classes/market.js.map +1 -1
- package/dist/classes/reserve.d.ts +2 -2
- package/dist/classes/reserve.d.ts.map +1 -1
- package/dist/classes/reserve.js +2 -2
- package/dist/classes/reserve.js.map +1 -1
- package/dist/classes/vault.d.ts +20 -9
- package/dist/classes/vault.d.ts.map +1 -1
- package/dist/classes/vault.js +72 -24
- package/dist/classes/vault.js.map +1 -1
- package/dist/client/commands/initFarmsForReserve.d.ts.map +1 -1
- package/dist/client/commands/initFarmsForReserve.js +1 -2
- package/dist/client/commands/initFarmsForReserve.js.map +1 -1
- package/dist/manager/client_kamino_manager.js +4 -0
- package/dist/manager/client_kamino_manager.js.map +1 -1
- package/dist/utils/farmUtils.d.ts +10 -0
- package/dist/utils/farmUtils.d.ts.map +1 -0
- package/dist/utils/farmUtils.js +41 -0
- package/dist/utils/farmUtils.js.map +1 -0
- package/dist/utils/oracle.d.ts +1 -1
- package/dist/utils/oracle.d.ts.map +1 -1
- package/dist/utils/oracle.js +2 -2
- package/dist/utils/oracle.js.map +1 -1
- package/package.json +1 -1
- package/src/classes/manager.ts +30 -64
- package/src/classes/market.ts +91 -21
- package/src/classes/reserve.ts +4 -2
- package/src/classes/vault.ts +126 -26
- package/src/client/commands/initFarmsForReserve.ts +1 -1
- package/src/manager/client_kamino_manager.ts +4 -0
- package/src/utils/farmUtils.ts +73 -0
- package/src/utils/oracle.ts +3 -2
package/src/classes/market.ts
CHANGED
|
@@ -20,6 +20,7 @@ import { KaminoObligation } from './obligation';
|
|
|
20
20
|
import { KaminoReserve, KaminoReserveRpcApi } from './reserve';
|
|
21
21
|
import { LendingMarket, Obligation, ReferrerTokenState, Reserve, UserMetadata } from '../@codegen/klend/accounts';
|
|
22
22
|
import {
|
|
23
|
+
AllOracleAccounts,
|
|
23
24
|
cacheOrGetPythPrices,
|
|
24
25
|
cacheOrGetScopePrice,
|
|
25
26
|
cacheOrGetSwitchboardPrice,
|
|
@@ -48,7 +49,7 @@ import { PROGRAM_ID } from '../@codegen/klend/programId';
|
|
|
48
49
|
import { Scope, U16_MAX } from '@kamino-finance/scope-sdk';
|
|
49
50
|
import { OraclePrices } from '@kamino-finance/scope-sdk/dist/@codegen/scope/accounts/OraclePrices';
|
|
50
51
|
import { Fraction } from './fraction';
|
|
51
|
-
import { chunks, KaminoPrices, MintToPriceMap } from '@kamino-finance/kliquidity-sdk';
|
|
52
|
+
import { batchFetch, chunks, KaminoPrices, MintToPriceMap } from '@kamino-finance/kliquidity-sdk';
|
|
52
53
|
import { parseTokenSymbol, parseZeroPaddedUtf8 } from './utils';
|
|
53
54
|
import { ObligationZP } from '../@codegen/klend/zero_padding';
|
|
54
55
|
import { checkDefined } from '../utils/validations';
|
|
@@ -99,6 +100,10 @@ export class KaminoMarket {
|
|
|
99
100
|
recentSlotDurationMs: number,
|
|
100
101
|
programId: Address = PROGRAM_ID
|
|
101
102
|
) {
|
|
103
|
+
if (recentSlotDurationMs <= 0) {
|
|
104
|
+
throw new Error('Recent slot duration cannot be 0');
|
|
105
|
+
}
|
|
106
|
+
|
|
102
107
|
this.address = marketAddress;
|
|
103
108
|
this.rpc = rpc;
|
|
104
109
|
this.state = state;
|
|
@@ -134,10 +139,6 @@ export class KaminoMarket {
|
|
|
134
139
|
return null;
|
|
135
140
|
}
|
|
136
141
|
|
|
137
|
-
if (recentSlotDurationMs <= 0) {
|
|
138
|
-
throw new Error('Recent slot duration cannot be 0');
|
|
139
|
-
}
|
|
140
|
-
|
|
141
142
|
const reserves = withReserves
|
|
142
143
|
? await getReservesForMarket(marketAddress, rpc, programId, recentSlotDurationMs)
|
|
143
144
|
: new Map<Address, KaminoReserve>();
|
|
@@ -156,6 +157,68 @@ export class KaminoMarket {
|
|
|
156
157
|
return new KaminoMarket(connection, market, marketAddress, reserves, recentSlotDurationMs, programId);
|
|
157
158
|
}
|
|
158
159
|
|
|
160
|
+
static async loadMultiple(
|
|
161
|
+
connection: Rpc<KaminoMarketRpcApi>,
|
|
162
|
+
markets: Address[],
|
|
163
|
+
recentSlotDurationMs: number,
|
|
164
|
+
programId: Address = PROGRAM_ID,
|
|
165
|
+
withReserves: boolean = true,
|
|
166
|
+
oracleAccounts?: AllOracleAccounts
|
|
167
|
+
) {
|
|
168
|
+
const marketStates = await batchFetch(markets, (market) =>
|
|
169
|
+
LendingMarket.fetchMultiple(connection, market, programId)
|
|
170
|
+
);
|
|
171
|
+
const kaminoMarkets = new Map<Address, KaminoMarket>();
|
|
172
|
+
for (let i = 0; i < markets.length; i++) {
|
|
173
|
+
const market = marketStates[i];
|
|
174
|
+
const marketAddress = markets[i];
|
|
175
|
+
if (market === null) {
|
|
176
|
+
throw Error(`Could not fetch LendingMarket account state for market ${marketAddress}`);
|
|
177
|
+
}
|
|
178
|
+
|
|
179
|
+
const marketReserves = withReserves
|
|
180
|
+
? await getReservesForMarket(marketAddress, connection, programId, recentSlotDurationMs, oracleAccounts)
|
|
181
|
+
: new Map<Address, KaminoReserve>();
|
|
182
|
+
|
|
183
|
+
kaminoMarkets.set(
|
|
184
|
+
marketAddress,
|
|
185
|
+
new KaminoMarket(connection, market, marketAddress, marketReserves, recentSlotDurationMs, programId)
|
|
186
|
+
);
|
|
187
|
+
}
|
|
188
|
+
return kaminoMarkets;
|
|
189
|
+
}
|
|
190
|
+
|
|
191
|
+
static async loadMultipleWithReserves(
|
|
192
|
+
connection: Rpc<KaminoMarketRpcApi>,
|
|
193
|
+
markets: Address[],
|
|
194
|
+
reserves: Map<Address, Map<Address, KaminoReserve>>,
|
|
195
|
+
recentSlotDurationMs: number,
|
|
196
|
+
programId: Address = PROGRAM_ID
|
|
197
|
+
) {
|
|
198
|
+
const marketStates = await batchFetch(markets, (market) =>
|
|
199
|
+
LendingMarket.fetchMultiple(connection, market, programId)
|
|
200
|
+
);
|
|
201
|
+
const kaminoMarkets = new Map<Address, KaminoMarket>();
|
|
202
|
+
for (let i = 0; i < markets.length; i++) {
|
|
203
|
+
const market = marketStates[i];
|
|
204
|
+
const marketAddress = markets[i];
|
|
205
|
+
if (market === null) {
|
|
206
|
+
throw Error(`Could not fetch LendingMarket account state for market ${marketAddress}`);
|
|
207
|
+
}
|
|
208
|
+
const marketReserves = reserves.get(marketAddress);
|
|
209
|
+
if (!marketReserves) {
|
|
210
|
+
throw Error(
|
|
211
|
+
`Could not get reserves for market ${marketAddress} from the reserves map argument supplied to this method`
|
|
212
|
+
);
|
|
213
|
+
}
|
|
214
|
+
kaminoMarkets.set(
|
|
215
|
+
marketAddress,
|
|
216
|
+
new KaminoMarket(connection, market, marketAddress, marketReserves, recentSlotDurationMs, programId)
|
|
217
|
+
);
|
|
218
|
+
}
|
|
219
|
+
return kaminoMarkets;
|
|
220
|
+
}
|
|
221
|
+
|
|
159
222
|
async reload(): Promise<void> {
|
|
160
223
|
const market = await LendingMarket.fetch(this.rpc, this.getAddress(), this.programId);
|
|
161
224
|
if (market === null) {
|
|
@@ -395,7 +458,7 @@ export class KaminoMarket {
|
|
|
395
458
|
: debtReserve.getMaxBorrowAmountWithCollReserve(this, collReserve, slot);
|
|
396
459
|
}
|
|
397
460
|
|
|
398
|
-
async loadReserves() {
|
|
461
|
+
async loadReserves(oracleAccounts?: AllOracleAccounts) {
|
|
399
462
|
const addresses = [...this.reserves.keys()];
|
|
400
463
|
const reserveAccounts = await this.rpc
|
|
401
464
|
.getMultipleAccounts(addresses, { commitment: 'processed', encoding: 'base64' })
|
|
@@ -411,7 +474,7 @@ export class KaminoMarket {
|
|
|
411
474
|
}
|
|
412
475
|
return reserveAccount;
|
|
413
476
|
});
|
|
414
|
-
const reservesAndOracles = await getTokenOracleData(this.getRpc(), deserializedReserves);
|
|
477
|
+
const reservesAndOracles = await getTokenOracleData(this.getRpc(), deserializedReserves, oracleAccounts);
|
|
415
478
|
const kaminoReserves = new Map<Address, KaminoReserve>();
|
|
416
479
|
reservesAndOracles.forEach(([reserve, oracle], index) => {
|
|
417
480
|
if (!oracle) {
|
|
@@ -881,9 +944,13 @@ export class KaminoMarket {
|
|
|
881
944
|
return finalObligations;
|
|
882
945
|
}
|
|
883
946
|
|
|
884
|
-
async getAllUserObligations(
|
|
947
|
+
async getAllUserObligations(
|
|
948
|
+
user: Address,
|
|
949
|
+
commitment: Commitment = 'processed',
|
|
950
|
+
slot?: bigint
|
|
951
|
+
): Promise<KaminoObligation[]> {
|
|
885
952
|
const [currentSlot, obligations] = await Promise.all([
|
|
886
|
-
this.rpc.getSlot().send(),
|
|
953
|
+
slot !== undefined ? Promise.resolve(slot) : this.rpc.getSlot().send(),
|
|
887
954
|
this.rpc
|
|
888
955
|
.getProgramAccounts(this.programId, {
|
|
889
956
|
filters: [
|
|
@@ -1261,8 +1328,7 @@ export class KaminoMarket {
|
|
|
1261
1328
|
/**
|
|
1262
1329
|
* Get all Scope prices used by all the market reserves
|
|
1263
1330
|
*/
|
|
1264
|
-
async getAllScopePrices(scope: Scope): Promise<KaminoPrices> {
|
|
1265
|
-
const allOraclePrices = await this.getReserveOraclePrices(scope);
|
|
1331
|
+
async getAllScopePrices(scope: Scope, allOraclePrices: Map<Address, OraclePrices>): Promise<KaminoPrices> {
|
|
1266
1332
|
const spot: MintToPriceMap = {};
|
|
1267
1333
|
const twaps: MintToPriceMap = {};
|
|
1268
1334
|
for (const reserve of this.reserves.values()) {
|
|
@@ -1271,7 +1337,7 @@ export class KaminoMarket {
|
|
|
1271
1337
|
const oracle = reserve.state.config.tokenInfo.scopeConfiguration.priceFeed;
|
|
1272
1338
|
const chain = reserve.state.config.tokenInfo.scopeConfiguration.priceChain;
|
|
1273
1339
|
const twapChain = reserve.state.config.tokenInfo.scopeConfiguration.twapChain.filter((x) => x > 0);
|
|
1274
|
-
const oraclePrices = allOraclePrices.get(
|
|
1340
|
+
const oraclePrices = allOraclePrices.get(oracle);
|
|
1275
1341
|
if (oraclePrices && oracle && isNotNullPubkey(oracle) && chain && Scope.isScopeChainValid(chain)) {
|
|
1276
1342
|
const spotPrice = await scope.getPriceFromChain(chain, oraclePrices);
|
|
1277
1343
|
spot[tokenMint] = { price: spotPrice.price, name: tokenName };
|
|
@@ -1287,16 +1353,18 @@ export class KaminoMarket {
|
|
|
1287
1353
|
/**
|
|
1288
1354
|
* Get all Scope/Pyth/Switchboard prices used by all the market reserves
|
|
1289
1355
|
*/
|
|
1290
|
-
async getAllPrices(): Promise<KlendPrices> {
|
|
1356
|
+
async getAllPrices(oracleAccounts?: AllOracleAccounts): Promise<KlendPrices> {
|
|
1291
1357
|
const klendPrices: KlendPrices = {
|
|
1292
1358
|
scope: { spot: {}, twap: {} },
|
|
1293
1359
|
pyth: { spot: {}, twap: {} },
|
|
1294
1360
|
switchboard: { spot: {}, twap: {} },
|
|
1295
1361
|
};
|
|
1296
|
-
const allOracleAccounts =
|
|
1297
|
-
|
|
1298
|
-
|
|
1299
|
-
|
|
1362
|
+
const allOracleAccounts =
|
|
1363
|
+
oracleAccounts ??
|
|
1364
|
+
(await getAllOracleAccounts(
|
|
1365
|
+
this.rpc,
|
|
1366
|
+
this.getReserves().map((x) => x.state)
|
|
1367
|
+
));
|
|
1300
1368
|
const pythCache = new Map<Address, PythPrices>();
|
|
1301
1369
|
const switchboardCache = new Map<Address, CandidatePrice>();
|
|
1302
1370
|
const scopeCache = new Map<Address, OraclePrices>();
|
|
@@ -1506,7 +1574,8 @@ export async function getReservesForMarket(
|
|
|
1506
1574
|
marketAddress: Address,
|
|
1507
1575
|
rpc: Rpc<KaminoReserveRpcApi>,
|
|
1508
1576
|
programId: Address,
|
|
1509
|
-
recentSlotDurationMs: number
|
|
1577
|
+
recentSlotDurationMs: number,
|
|
1578
|
+
oracleAccounts?: AllOracleAccounts
|
|
1510
1579
|
): Promise<Map<Address, KaminoReserve>> {
|
|
1511
1580
|
const reserves = await rpc
|
|
1512
1581
|
.getProgramAccounts(programId, {
|
|
@@ -1537,7 +1606,7 @@ export async function getReservesForMarket(
|
|
|
1537
1606
|
}
|
|
1538
1607
|
return reserveAccount;
|
|
1539
1608
|
});
|
|
1540
|
-
const reservesAndOracles = await getTokenOracleData(rpc, deserializedReserves);
|
|
1609
|
+
const reservesAndOracles = await getTokenOracleData(rpc, deserializedReserves, oracleAccounts);
|
|
1541
1610
|
const reservesByAddress = new Map<Address, KaminoReserve>();
|
|
1542
1611
|
reservesAndOracles.forEach(([reserve, oracle], index) => {
|
|
1543
1612
|
if (!oracle) {
|
|
@@ -1553,14 +1622,15 @@ export async function getSingleReserve(
|
|
|
1553
1622
|
reservePk: Address,
|
|
1554
1623
|
rpc: Rpc<KaminoReserveRpcApi>,
|
|
1555
1624
|
recentSlotDurationMs: number,
|
|
1556
|
-
reserveData?: Reserve
|
|
1625
|
+
reserveData?: Reserve,
|
|
1626
|
+
oracleAccounts?: AllOracleAccounts
|
|
1557
1627
|
): Promise<KaminoReserve> {
|
|
1558
1628
|
const reserve = reserveData ?? (await Reserve.fetch(rpc, reservePk));
|
|
1559
1629
|
|
|
1560
1630
|
if (reserve === null) {
|
|
1561
1631
|
throw new Error(`Reserve account ${reservePk} does not exist`);
|
|
1562
1632
|
}
|
|
1563
|
-
const reservesAndOracles = await getTokenOracleData(rpc, [reserve]);
|
|
1633
|
+
const reservesAndOracles = await getTokenOracleData(rpc, [reserve], oracleAccounts);
|
|
1564
1634
|
const [, oracle] = reservesAndOracles[0];
|
|
1565
1635
|
|
|
1566
1636
|
if (!oracle) {
|
package/src/classes/reserve.ts
CHANGED
|
@@ -16,6 +16,7 @@ import {
|
|
|
16
16
|
} from '@solana/kit';
|
|
17
17
|
import Decimal from 'decimal.js';
|
|
18
18
|
import {
|
|
19
|
+
AllOracleAccounts,
|
|
19
20
|
DEFAULT_PUBLIC_KEY,
|
|
20
21
|
getTokenOracleData,
|
|
21
22
|
globalConfigPda,
|
|
@@ -102,14 +103,15 @@ export class KaminoReserve {
|
|
|
102
103
|
address: Address,
|
|
103
104
|
rpc: Rpc<KaminoReserveRpcApi>,
|
|
104
105
|
recentSlotDurationMs: number,
|
|
105
|
-
reserveState?: Reserve
|
|
106
|
+
reserveState?: Reserve,
|
|
107
|
+
oracleAccounts?: AllOracleAccounts
|
|
106
108
|
) {
|
|
107
109
|
const reserve = reserveState ?? (await Reserve.fetch(rpc, address));
|
|
108
110
|
if (reserve === null) {
|
|
109
111
|
throw new Error(`Reserve account ${address} does not exist`);
|
|
110
112
|
}
|
|
111
113
|
|
|
112
|
-
const tokenOracleDataWithReserve = await getTokenOracleData(rpc, [reserve]);
|
|
114
|
+
const tokenOracleDataWithReserve = await getTokenOracleData(rpc, [reserve], oracleAccounts);
|
|
113
115
|
if (!tokenOracleDataWithReserve[0]) {
|
|
114
116
|
throw new Error('Token oracle data not found');
|
|
115
117
|
}
|
package/src/classes/vault.ts
CHANGED
|
@@ -23,6 +23,7 @@ import {
|
|
|
23
23
|
TransactionSigner,
|
|
24
24
|
} from '@solana/kit';
|
|
25
25
|
import {
|
|
26
|
+
AllOracleAccounts,
|
|
26
27
|
DEFAULT_PUBLIC_KEY,
|
|
27
28
|
DEFAULT_RECENT_SLOT_DURATION_MS,
|
|
28
29
|
getAssociatedTokenAddress,
|
|
@@ -124,6 +125,7 @@ import { getExtendLookupTableInstruction } from '@solana-program/address-lookup-
|
|
|
124
125
|
import { Farms } from '@kamino-finance/farms-sdk';
|
|
125
126
|
import { getFarmIncentives } from '@kamino-finance/farms-sdk/dist/utils/apy';
|
|
126
127
|
import { computeReservesAllocation } from '../utils/vaultAllocation';
|
|
128
|
+
import { getReserveFarmRewardsAPY } from '../utils/farmUtils';
|
|
127
129
|
|
|
128
130
|
export const kaminoVaultId = address('KvauGMspG5k6rtzrqqn7WNn3oZdyKqLKwK2XWQ8FLjd');
|
|
129
131
|
export const kaminoVaultStagingId = address('stKvQfwRsQiKnLtMNVLHKS3exFJmZFsgfzBPWHECUYK');
|
|
@@ -2064,19 +2066,21 @@ export class KaminoVaultClient {
|
|
|
2064
2066
|
|
|
2065
2067
|
/**
|
|
2066
2068
|
* This method calculates the token per share value. This will always change based on interest earned from the vault, but calculating it requires a bunch of rpc requests. Caching this for a short duration would be optimal
|
|
2067
|
-
* @param
|
|
2069
|
+
* @param vaultState - vault state to calculate tokensPerShare for
|
|
2068
2070
|
* @param [slot] - the slot at which we retrieve the tokens per share. Optional. If not provided, the function will fetch the current slot
|
|
2069
2071
|
* @param [vaultReservesMap] - hashmap from each reserve pubkey to the reserve state. Optional. If provided the function will be significantly faster as it will not have to fetch the reserves
|
|
2070
2072
|
* @param [currentSlot] - the latest confirmed slot. Optional. If provided the function will be faster as it will not have to fetch the latest slot
|
|
2071
2073
|
* @returns - token per share value
|
|
2072
2074
|
*/
|
|
2073
2075
|
async getTokensPerShareSingleVault(
|
|
2074
|
-
|
|
2076
|
+
vaultOrState: KaminoVault | VaultState,
|
|
2075
2077
|
slot?: Slot,
|
|
2076
2078
|
vaultReservesMap?: Map<Address, KaminoReserve>,
|
|
2077
2079
|
currentSlot?: Slot
|
|
2078
2080
|
): Promise<Decimal> {
|
|
2079
|
-
|
|
2081
|
+
// Determine if we have a KaminoVault or VaultState
|
|
2082
|
+
const vaultState = 'getState' in vaultOrState ? await vaultOrState.getState(this.getConnection()) : vaultOrState;
|
|
2083
|
+
|
|
2080
2084
|
if (vaultState.sharesIssued.isZero()) {
|
|
2081
2085
|
return new Decimal(0);
|
|
2082
2086
|
}
|
|
@@ -2361,9 +2365,13 @@ export class KaminoVaultClient {
|
|
|
2361
2365
|
/**
|
|
2362
2366
|
* This will load the onchain state for all the reserves that the vaults have allocations for, deduplicating the reserves
|
|
2363
2367
|
* @param vaults - the vault states to load reserves for
|
|
2368
|
+
* @param oracleAccounts (optional) all reserve oracle accounts, if not supplied will make an additional rpc call to fetch these accounts
|
|
2364
2369
|
* @returns a hashmap from each reserve pubkey to the reserve state
|
|
2365
2370
|
*/
|
|
2366
|
-
async loadVaultsReserves(
|
|
2371
|
+
async loadVaultsReserves(
|
|
2372
|
+
vaults: VaultState[],
|
|
2373
|
+
oracleAccounts?: AllOracleAccounts
|
|
2374
|
+
): Promise<Map<Address, KaminoReserve>> {
|
|
2367
2375
|
const vaultReservesAddressesSet = new Set<Address>(vaults.flatMap((vault) => this.getVaultReserves(vault)));
|
|
2368
2376
|
const vaultReservesAddresses = [...vaultReservesAddressesSet];
|
|
2369
2377
|
const reserveAccounts = await this.getConnection()
|
|
@@ -2382,7 +2390,7 @@ export class KaminoVaultClient {
|
|
|
2382
2390
|
return reserveAccount;
|
|
2383
2391
|
});
|
|
2384
2392
|
|
|
2385
|
-
const reservesAndOracles = await getTokenOracleData(this.getConnection(), deserializedReserves);
|
|
2393
|
+
const reservesAndOracles = await getTokenOracleData(this.getConnection(), deserializedReserves, oracleAccounts);
|
|
2386
2394
|
|
|
2387
2395
|
const kaminoReserves = new Map<Address, KaminoReserve>();
|
|
2388
2396
|
|
|
@@ -2409,13 +2417,15 @@ export class KaminoVaultClient {
|
|
|
2409
2417
|
* @param [slot] - the slot for which to retrieve the vault collaterals for. Optional. If not provided the function will fetch the current slot
|
|
2410
2418
|
* @param [vaultReservesMap] - hashmap from each reserve pubkey to the reserve state. Optional. If provided the function will be significantly faster as it will not have to fetch the reserves
|
|
2411
2419
|
* @param [kaminoMarkets] - a list of all the kamino markets. Optional. If provided the function will be significantly faster as it will not have to fetch the markets
|
|
2420
|
+
* @param oracleAccounts (optional) all reserve oracle accounts, if not supplied will make an additional rpc call to fetch these accounts
|
|
2412
2421
|
* @returns a hashmap from each reserve pubkey to the market overview of the collaterals that can be used and the min and max loan to value ratio in that market
|
|
2413
2422
|
*/
|
|
2414
2423
|
async getVaultCollaterals(
|
|
2415
2424
|
vaultState: VaultState,
|
|
2416
2425
|
slot: Slot,
|
|
2417
2426
|
vaultReservesMap?: Map<Address, KaminoReserve>,
|
|
2418
|
-
kaminoMarkets?: KaminoMarket[]
|
|
2427
|
+
kaminoMarkets?: KaminoMarket[],
|
|
2428
|
+
oracleAccounts?: AllOracleAccounts
|
|
2419
2429
|
): Promise<Map<Address, MarketOverview>> {
|
|
2420
2430
|
const vaultReservesStateMap = vaultReservesMap ? vaultReservesMap : await this.loadVaultReserves(vaultState);
|
|
2421
2431
|
const vaultReservesState: KaminoReserve[] = [];
|
|
@@ -2439,7 +2449,11 @@ export class KaminoVaultClient {
|
|
|
2439
2449
|
const missingReservesStates = (await Reserve.fetchMultiple(this.getConnection(), [...missingReserves])).filter(
|
|
2440
2450
|
(reserve) => reserve !== null
|
|
2441
2451
|
);
|
|
2442
|
-
const missingReservesAndOracles = await getTokenOracleData(
|
|
2452
|
+
const missingReservesAndOracles = await getTokenOracleData(
|
|
2453
|
+
this.getConnection(),
|
|
2454
|
+
missingReservesStates,
|
|
2455
|
+
oracleAccounts
|
|
2456
|
+
);
|
|
2443
2457
|
missingReservesAndOracles.forEach(([reserve, oracle], index) => {
|
|
2444
2458
|
const fetchedReserve = new KaminoReserve(
|
|
2445
2459
|
reserve,
|
|
@@ -2638,16 +2652,16 @@ export class KaminoVaultClient {
|
|
|
2638
2652
|
/**
|
|
2639
2653
|
* This will return an VaultOverview object that encapsulates all the information about the vault, including the holdings, reserves details, theoretical APY, utilization ratio and total borrowed amount
|
|
2640
2654
|
* @param vault - the kamino vault to get available liquidity to withdraw for
|
|
2641
|
-
* @param
|
|
2655
|
+
* @param vaultTokenPrice - the price of the token in the vault (e.g. USDC)
|
|
2642
2656
|
* @param [slot] - the slot for which to retrieve the vault overview for. Optional. If not provided the function will fetch the current slot
|
|
2643
2657
|
* @param [vaultReservesMap] - hashmap from each reserve pubkey to the reserve state. Optional. If provided the function will be significantly faster as it will not have to fetch the reserves
|
|
2644
2658
|
* @param [kaminoMarkets] - a list of all kamino markets. Optional. If provided the function will be significantly faster as it will not have to fetch the markets
|
|
2645
2659
|
* @param [currentSlot] - the latest confirmed slot. Optional. If provided the function will be faster as it will not have to fetch the latest slot
|
|
2646
|
-
* @returns an VaultOverview object with details about the tokens available and invested in the vault, denominated in tokens and USD
|
|
2660
|
+
* @returns an VaultOverview object with details about the tokens available and invested in the vault, denominated in tokens and USD, along sie APYs
|
|
2647
2661
|
*/
|
|
2648
2662
|
async getVaultOverview(
|
|
2649
2663
|
vault: VaultState,
|
|
2650
|
-
|
|
2664
|
+
vaultTokenPrice: Decimal,
|
|
2651
2665
|
slot?: Slot,
|
|
2652
2666
|
vaultReservesMap?: Map<Address, KaminoReserve>,
|
|
2653
2667
|
kaminoMarkets?: KaminoMarket[],
|
|
@@ -2655,30 +2669,34 @@ export class KaminoVaultClient {
|
|
|
2655
2669
|
): Promise<VaultOverview> {
|
|
2656
2670
|
const vaultReservesState = vaultReservesMap ? vaultReservesMap : await this.loadVaultReserves(vault);
|
|
2657
2671
|
|
|
2658
|
-
const vaultHoldingsWithUSDValuePromise =
|
|
2672
|
+
const vaultHoldingsWithUSDValuePromise = this.getVaultHoldingsWithPrice(
|
|
2659
2673
|
vault,
|
|
2660
|
-
|
|
2674
|
+
vaultTokenPrice,
|
|
2661
2675
|
slot,
|
|
2662
2676
|
vaultReservesState,
|
|
2663
2677
|
currentSlot
|
|
2664
2678
|
);
|
|
2665
2679
|
|
|
2666
2680
|
const slotForOverview = slot ? slot : await this.getConnection().getSlot().send();
|
|
2681
|
+
const farmsClient = new Farms(this.getConnection());
|
|
2667
2682
|
|
|
2668
|
-
const vaultTheoreticalAPYPromise =
|
|
2669
|
-
const vaultActualAPYPromise =
|
|
2670
|
-
const totalInvestedAndBorrowedPromise =
|
|
2683
|
+
const vaultTheoreticalAPYPromise = this.getVaultTheoreticalAPY(vault, slotForOverview, vaultReservesState);
|
|
2684
|
+
const vaultActualAPYPromise = this.getVaultActualAPY(vault, slotForOverview, vaultReservesState);
|
|
2685
|
+
const totalInvestedAndBorrowedPromise = this.getTotalBorrowedAndInvested(
|
|
2671
2686
|
vault,
|
|
2672
2687
|
slotForOverview,
|
|
2673
2688
|
vaultReservesState
|
|
2674
2689
|
);
|
|
2675
|
-
const vaultCollateralsPromise =
|
|
2690
|
+
const vaultCollateralsPromise = this.getVaultCollaterals(vault, slotForOverview, vaultReservesState, kaminoMarkets);
|
|
2691
|
+
const reservesOverviewPromise = this.getVaultReservesDetails(vault, slotForOverview, vaultReservesState);
|
|
2692
|
+
const vaultFarmIncentivesPromise = this.getVaultRewardsAPY(vault, vaultTokenPrice, farmsClient, slotForOverview);
|
|
2693
|
+
const vaultReservesFarmIncentivesPromise = this.getVaultReservesFarmsIncentives(
|
|
2676
2694
|
vault,
|
|
2695
|
+
vaultTokenPrice,
|
|
2696
|
+
farmsClient,
|
|
2677
2697
|
slotForOverview,
|
|
2678
|
-
vaultReservesState
|
|
2679
|
-
kaminoMarkets
|
|
2698
|
+
vaultReservesState
|
|
2680
2699
|
);
|
|
2681
|
-
const reservesOverviewPromise = await this.getVaultReservesDetails(vault, slotForOverview, vaultReservesState);
|
|
2682
2700
|
|
|
2683
2701
|
// all the async part of the functions above just read the vaultReservesState which is read beforehand, so excepting vaultCollateralsPromise they should do no additional network calls
|
|
2684
2702
|
const [
|
|
@@ -2688,6 +2706,8 @@ export class KaminoVaultClient {
|
|
|
2688
2706
|
totalInvestedAndBorrowed,
|
|
2689
2707
|
vaultCollaterals,
|
|
2690
2708
|
reservesOverview,
|
|
2709
|
+
vaultFarmIncentives,
|
|
2710
|
+
vaultReservesFarmIncentives,
|
|
2691
2711
|
] = await Promise.all([
|
|
2692
2712
|
vaultHoldingsWithUSDValuePromise,
|
|
2693
2713
|
vaultTheoreticalAPYPromise,
|
|
@@ -2695,6 +2715,8 @@ export class KaminoVaultClient {
|
|
|
2695
2715
|
totalInvestedAndBorrowedPromise,
|
|
2696
2716
|
vaultCollateralsPromise,
|
|
2697
2717
|
reservesOverviewPromise,
|
|
2718
|
+
vaultFarmIncentivesPromise,
|
|
2719
|
+
vaultReservesFarmIncentivesPromise,
|
|
2698
2720
|
]);
|
|
2699
2721
|
|
|
2700
2722
|
return {
|
|
@@ -2703,11 +2725,13 @@ export class KaminoVaultClient {
|
|
|
2703
2725
|
vaultCollaterals: vaultCollaterals,
|
|
2704
2726
|
actualSupplyAPY: vaultActualAPYs,
|
|
2705
2727
|
theoreticalSupplyAPY: vaultTheoreticalAPYs,
|
|
2728
|
+
vaultFarmIncentives: vaultFarmIncentives,
|
|
2729
|
+
reservesFarmsIncentives: vaultReservesFarmIncentives,
|
|
2706
2730
|
totalBorrowed: totalInvestedAndBorrowed.totalBorrowed,
|
|
2707
|
-
totalBorrowedUSD: totalInvestedAndBorrowed.totalBorrowed.mul(
|
|
2731
|
+
totalBorrowedUSD: totalInvestedAndBorrowed.totalBorrowed.mul(vaultTokenPrice),
|
|
2708
2732
|
utilizationRatio: totalInvestedAndBorrowed.utilizationRatio,
|
|
2709
2733
|
totalSupplied: totalInvestedAndBorrowed.totalInvested,
|
|
2710
|
-
totalSuppliedUSD: totalInvestedAndBorrowed.totalInvested.mul(
|
|
2734
|
+
totalSuppliedUSD: totalInvestedAndBorrowed.totalInvested.mul(vaultTokenPrice),
|
|
2711
2735
|
};
|
|
2712
2736
|
}
|
|
2713
2737
|
|
|
@@ -3021,11 +3045,18 @@ export class KaminoVaultClient {
|
|
|
3021
3045
|
* Read the APY of the farm built on top of the vault (farm in vaultState.vaultFarm)
|
|
3022
3046
|
* @param vault - the vault to read the farm APY for
|
|
3023
3047
|
* @param vaultTokenPrice - the price of the vault token in USD (e.g. 1.0 for USDC)
|
|
3048
|
+
* @param [farmsClient] - the farms client to use. Optional. If not provided, the function will create a new one
|
|
3024
3049
|
* @param [slot] - the slot to read the farm APY for. Optional. If not provided, the function will read the current slot
|
|
3025
3050
|
* @returns the APY of the farm built on top of the vault
|
|
3026
3051
|
*/
|
|
3027
|
-
async getVaultRewardsAPY(
|
|
3028
|
-
|
|
3052
|
+
async getVaultRewardsAPY(
|
|
3053
|
+
vaultOrState: KaminoVault | VaultState,
|
|
3054
|
+
vaultTokenPrice: Decimal,
|
|
3055
|
+
farmsClient?: Farms,
|
|
3056
|
+
slot?: Slot
|
|
3057
|
+
): Promise<FarmIncentives> {
|
|
3058
|
+
// Determine if we have a KaminoVault or VaultState
|
|
3059
|
+
const vaultState = 'getState' in vaultOrState ? await vaultOrState.getState(this.getConnection()) : vaultOrState;
|
|
3029
3060
|
if (vaultState.vaultFarm === DEFAULT_PUBLIC_KEY) {
|
|
3030
3061
|
return {
|
|
3031
3062
|
incentivesStats: [],
|
|
@@ -3033,12 +3064,74 @@ export class KaminoVaultClient {
|
|
|
3033
3064
|
};
|
|
3034
3065
|
}
|
|
3035
3066
|
|
|
3036
|
-
const tokensPerShare = await this.getTokensPerShareSingleVault(
|
|
3067
|
+
const tokensPerShare = await this.getTokensPerShareSingleVault(vaultState, slot);
|
|
3037
3068
|
const sharePrice = tokensPerShare.mul(vaultTokenPrice);
|
|
3038
3069
|
const stakedTokenMintDecimals = vaultState.sharesMintDecimals.toNumber();
|
|
3039
3070
|
|
|
3040
|
-
const
|
|
3041
|
-
return getFarmIncentives(
|
|
3071
|
+
const kFarmsClient = farmsClient ? farmsClient : new Farms(this.getConnection());
|
|
3072
|
+
return getFarmIncentives(kFarmsClient, vaultState.vaultFarm, sharePrice, stakedTokenMintDecimals);
|
|
3073
|
+
}
|
|
3074
|
+
|
|
3075
|
+
async getVaultReservesFarmsIncentives(
|
|
3076
|
+
vaultOrState: KaminoVault | VaultState,
|
|
3077
|
+
vaultTokenPrice: Decimal,
|
|
3078
|
+
farmsClient?: Farms,
|
|
3079
|
+
slot?: Slot,
|
|
3080
|
+
vaultReservesMap?: Map<Address, KaminoReserve>
|
|
3081
|
+
): Promise<VaultReservesFarmsIncentives> {
|
|
3082
|
+
const vaultState = 'getState' in vaultOrState ? await vaultOrState.getState(this.getConnection()) : vaultOrState;
|
|
3083
|
+
|
|
3084
|
+
const vaultReservesState = vaultReservesMap ? vaultReservesMap : await this.loadVaultReserves(vaultState);
|
|
3085
|
+
const currentSlot = slot ? slot : await this.getConnection().getSlot({ commitment: 'confirmed' }).send();
|
|
3086
|
+
|
|
3087
|
+
const holdings = await this.getVaultHoldings(vaultState, currentSlot, vaultReservesState);
|
|
3088
|
+
|
|
3089
|
+
const vaultReservesAddresses = vaultState.vaultAllocationStrategy.map(
|
|
3090
|
+
(allocationStrategy) => allocationStrategy.reserve
|
|
3091
|
+
);
|
|
3092
|
+
|
|
3093
|
+
const vaultReservesFarmsIncentives = new Map<Address, FarmIncentives>();
|
|
3094
|
+
let totalIncentivesApy = new Decimal(0);
|
|
3095
|
+
|
|
3096
|
+
const kFarmsClient = farmsClient ? farmsClient : new Farms(this.getConnection());
|
|
3097
|
+
for (const reserveAddress of vaultReservesAddresses) {
|
|
3098
|
+
if (reserveAddress === DEFAULT_PUBLIC_KEY) {
|
|
3099
|
+
continue;
|
|
3100
|
+
}
|
|
3101
|
+
|
|
3102
|
+
const reserveState = vaultReservesState.get(reserveAddress);
|
|
3103
|
+
if (reserveState === undefined) {
|
|
3104
|
+
console.log(`Reserve to read farm incentives for not found: ${reserveAddress}`);
|
|
3105
|
+
vaultReservesFarmsIncentives.set(reserveAddress, {
|
|
3106
|
+
incentivesStats: [],
|
|
3107
|
+
totalIncentivesApy: 0,
|
|
3108
|
+
});
|
|
3109
|
+
continue;
|
|
3110
|
+
}
|
|
3111
|
+
|
|
3112
|
+
const reserveFarmIncentives = await getReserveFarmRewardsAPY(
|
|
3113
|
+
this._rpc,
|
|
3114
|
+
this.recentSlotDurationMs,
|
|
3115
|
+
reserveAddress,
|
|
3116
|
+
vaultTokenPrice,
|
|
3117
|
+
this._kaminoLendProgramId,
|
|
3118
|
+
kFarmsClient,
|
|
3119
|
+
currentSlot,
|
|
3120
|
+
reserveState.state
|
|
3121
|
+
);
|
|
3122
|
+
vaultReservesFarmsIncentives.set(reserveAddress, reserveFarmIncentives.collateralFarmIncentives);
|
|
3123
|
+
|
|
3124
|
+
const investedInReserve = holdings.investedInReserves.get(reserveAddress);
|
|
3125
|
+
const weightedReserveAPY = new Decimal(reserveFarmIncentives.collateralFarmIncentives.totalIncentivesApy)
|
|
3126
|
+
.mul(investedInReserve ?? 0)
|
|
3127
|
+
.div(holdings.totalAUMIncludingFees);
|
|
3128
|
+
totalIncentivesApy = totalIncentivesApy.add(weightedReserveAPY);
|
|
3129
|
+
}
|
|
3130
|
+
|
|
3131
|
+
return {
|
|
3132
|
+
reserveFarmsIncentives: vaultReservesFarmsIncentives,
|
|
3133
|
+
totalIncentivesAPY: totalIncentivesApy,
|
|
3134
|
+
};
|
|
3042
3135
|
}
|
|
3043
3136
|
|
|
3044
3137
|
private appendRemainingAccountsForVaultReserves(
|
|
@@ -3275,6 +3368,8 @@ export type VaultOverview = {
|
|
|
3275
3368
|
vaultCollaterals: Map<Address, MarketOverview>;
|
|
3276
3369
|
theoreticalSupplyAPY: APYs;
|
|
3277
3370
|
actualSupplyAPY: APYs;
|
|
3371
|
+
vaultFarmIncentives: FarmIncentives;
|
|
3372
|
+
reservesFarmsIncentives: VaultReservesFarmsIncentives;
|
|
3278
3373
|
totalBorrowed: Decimal;
|
|
3279
3374
|
totalBorrowedUSD: Decimal;
|
|
3280
3375
|
totalSupplied: Decimal;
|
|
@@ -3282,6 +3377,11 @@ export type VaultOverview = {
|
|
|
3282
3377
|
utilizationRatio: Decimal;
|
|
3283
3378
|
};
|
|
3284
3379
|
|
|
3380
|
+
export type VaultReservesFarmsIncentives = {
|
|
3381
|
+
reserveFarmsIncentives: Map<Address, FarmIncentives>;
|
|
3382
|
+
totalIncentivesAPY: Decimal;
|
|
3383
|
+
};
|
|
3384
|
+
|
|
3285
3385
|
export type VaultFeesPct = {
|
|
3286
3386
|
managementFeePct: Decimal;
|
|
3287
3387
|
performanceFeePct: Decimal;
|
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
import { Address, generateKeyPairSigner, TransactionSigner } from '@solana/kit';
|
|
2
2
|
import {
|
|
3
|
+
DEFAULT_RECENT_SLOT_DURATION_MS,
|
|
3
4
|
initFarmsForReserve as initFarmsForReserveIx,
|
|
4
5
|
KaminoMarket,
|
|
5
6
|
lendingMarketAuthPda,
|
|
@@ -11,7 +12,6 @@ import { getCreateAccountInstruction, SYSTEM_PROGRAM_ADDRESS } from '@solana-pro
|
|
|
11
12
|
import { SYSVAR_RENT_ADDRESS } from '@solana/sysvars';
|
|
12
13
|
import { CliEnv, SendTxMode } from '../tx/CliEnv';
|
|
13
14
|
import { processTx } from '../tx/processor';
|
|
14
|
-
import { DEFAULT_RECENT_SLOT_DURATION_MS } from '@kamino-finance/klend-sdk';
|
|
15
15
|
|
|
16
16
|
export async function initFarmsForReserve(
|
|
17
17
|
env: CliEnv,
|
|
@@ -1318,6 +1318,10 @@ async function main() {
|
|
|
1318
1318
|
);
|
|
1319
1319
|
|
|
1320
1320
|
console.log('vaultOverview', vaultOverview);
|
|
1321
|
+
vaultOverview.reservesFarmsIncentives.reserveFarmsIncentives.forEach((incentive, reserveAddress) => {
|
|
1322
|
+
console.log('reserve ', reserveAddress);
|
|
1323
|
+
console.log('reserve incentive', incentive);
|
|
1324
|
+
});
|
|
1321
1325
|
});
|
|
1322
1326
|
|
|
1323
1327
|
commands
|
|
@@ -0,0 +1,73 @@
|
|
|
1
|
+
import { Address, Rpc, Slot, SolanaRpcApi } from '@solana/kit';
|
|
2
|
+
import { Decimal } from 'decimal.js';
|
|
3
|
+
import { FarmIncentives, Farms } from '@kamino-finance/farms-sdk';
|
|
4
|
+
import { getFarmIncentives } from '@kamino-finance/farms-sdk/dist/utils/apy';
|
|
5
|
+
import { DEFAULT_PUBLIC_KEY } from '@kamino-finance/farms-sdk';
|
|
6
|
+
import { Reserve } from '../@codegen/klend/accounts';
|
|
7
|
+
import { KaminoReserve } from '../lib';
|
|
8
|
+
import { getMintDecimals } from '@kamino-finance/kliquidity-sdk';
|
|
9
|
+
|
|
10
|
+
export interface ReserveIncentives {
|
|
11
|
+
collateralFarmIncentives: FarmIncentives;
|
|
12
|
+
debtFarmIncentives: FarmIncentives;
|
|
13
|
+
}
|
|
14
|
+
|
|
15
|
+
export async function getReserveFarmRewardsAPY(
|
|
16
|
+
rpc: Rpc<SolanaRpcApi>,
|
|
17
|
+
recentSlotDurationMs: number,
|
|
18
|
+
reserve: Address,
|
|
19
|
+
reserveLiquidityTokenPrice: Decimal,
|
|
20
|
+
kaminoLendProgramId: Address,
|
|
21
|
+
farmsClient: Farms,
|
|
22
|
+
slot: Slot,
|
|
23
|
+
reserveState?: Reserve,
|
|
24
|
+
cTokenMintDecimals?: number
|
|
25
|
+
): Promise<ReserveIncentives> {
|
|
26
|
+
const reserveIncentives: ReserveIncentives = {
|
|
27
|
+
collateralFarmIncentives: {
|
|
28
|
+
incentivesStats: [],
|
|
29
|
+
totalIncentivesApy: 0,
|
|
30
|
+
},
|
|
31
|
+
debtFarmIncentives: {
|
|
32
|
+
incentivesStats: [],
|
|
33
|
+
totalIncentivesApy: 0,
|
|
34
|
+
},
|
|
35
|
+
};
|
|
36
|
+
|
|
37
|
+
const reserveAccount = reserveState ? reserveState : await Reserve.fetch(rpc, reserve, kaminoLendProgramId);
|
|
38
|
+
if (!reserveAccount) {
|
|
39
|
+
throw new Error(`Reserve ${reserve} not found`);
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
const kaminoReserve = await KaminoReserve.initializeFromAddress(reserve, rpc, recentSlotDurationMs, reserveAccount);
|
|
43
|
+
|
|
44
|
+
const farmCollateral = kaminoReserve.state.farmCollateral;
|
|
45
|
+
const farmDebt = kaminoReserve.state.farmDebt;
|
|
46
|
+
|
|
47
|
+
const stakedTokenMintDecimals = kaminoReserve.getMintDecimals();
|
|
48
|
+
const reserveCtokenPrice = reserveLiquidityTokenPrice.div(kaminoReserve.getEstimatedCollateralExchangeRate(slot, 0));
|
|
49
|
+
const cTokenMint = kaminoReserve.getCTokenMint();
|
|
50
|
+
const cTokenDecimals = cTokenMintDecimals ? cTokenMintDecimals : await getMintDecimals(rpc, cTokenMint);
|
|
51
|
+
|
|
52
|
+
if (farmCollateral !== DEFAULT_PUBLIC_KEY) {
|
|
53
|
+
const farmIncentivesCollateral = await getFarmIncentives(
|
|
54
|
+
farmsClient,
|
|
55
|
+
farmCollateral,
|
|
56
|
+
reserveCtokenPrice,
|
|
57
|
+
cTokenDecimals
|
|
58
|
+
);
|
|
59
|
+
reserveIncentives.collateralFarmIncentives = farmIncentivesCollateral;
|
|
60
|
+
}
|
|
61
|
+
|
|
62
|
+
if (farmDebt !== DEFAULT_PUBLIC_KEY) {
|
|
63
|
+
const farmIncentivesDebt = await getFarmIncentives(
|
|
64
|
+
farmsClient,
|
|
65
|
+
farmDebt,
|
|
66
|
+
reserveLiquidityTokenPrice,
|
|
67
|
+
stakedTokenMintDecimals
|
|
68
|
+
);
|
|
69
|
+
reserveIncentives.debtFarmIncentives = farmIncentivesDebt;
|
|
70
|
+
}
|
|
71
|
+
|
|
72
|
+
return reserveIncentives;
|
|
73
|
+
}
|
package/src/utils/oracle.ts
CHANGED
|
@@ -105,9 +105,10 @@ export function getTokenOracleDataSync(allOracleAccounts: AllOracleAccounts, res
|
|
|
105
105
|
// TODO: Add freshness of the latest price to match sc logic
|
|
106
106
|
export async function getTokenOracleData(
|
|
107
107
|
rpc: Rpc<GetMultipleAccountsApi>,
|
|
108
|
-
reserves: Reserve[]
|
|
108
|
+
reserves: Reserve[],
|
|
109
|
+
oracleAccounts?: AllOracleAccounts
|
|
109
110
|
): Promise<Array<[Reserve, TokenOracleData | undefined]>> {
|
|
110
|
-
const allOracleAccounts = await getAllOracleAccounts(rpc, reserves);
|
|
111
|
+
const allOracleAccounts = oracleAccounts ?? (await getAllOracleAccounts(rpc, reserves));
|
|
111
112
|
return getTokenOracleDataSync(allOracleAccounts, reserves);
|
|
112
113
|
}
|
|
113
114
|
|