@kamino-finance/klend-sdk 5.4.0 → 5.4.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/dist/classes/action.d.ts +0 -3
- package/dist/classes/action.d.ts.map +1 -1
- package/dist/classes/action.js +7 -10
- package/dist/classes/action.js.map +1 -1
- package/dist/classes/manager.d.ts +15 -1
- package/dist/classes/manager.d.ts.map +1 -1
- package/dist/classes/manager.js +18 -0
- package/dist/classes/manager.js.map +1 -1
- package/dist/classes/market.d.ts +1 -0
- package/dist/classes/market.d.ts.map +1 -1
- package/dist/classes/market.js +45 -0
- package/dist/classes/market.js.map +1 -1
- package/dist/classes/vault.d.ts +22 -0
- package/dist/classes/vault.d.ts.map +1 -1
- package/dist/classes/vault.js +99 -22
- package/dist/classes/vault.js.map +1 -1
- package/dist/client_kamino_manager.d.ts.map +1 -1
- package/dist/client_kamino_manager.js +23 -4
- package/dist/client_kamino_manager.js.map +1 -1
- package/dist/idl_codegen_kamino_vault/accounts/VaultState.d.ts +6 -0
- package/dist/idl_codegen_kamino_vault/accounts/VaultState.d.ts.map +1 -1
- package/dist/idl_codegen_kamino_vault/accounts/VaultState.js +14 -2
- package/dist/idl_codegen_kamino_vault/accounts/VaultState.js.map +1 -1
- package/dist/idl_codegen_kamino_vault/types/VaultAllocation.d.ts +5 -0
- package/dist/idl_codegen_kamino_vault/types/VaultAllocation.d.ts.map +1 -1
- package/dist/idl_codegen_kamino_vault/types/VaultAllocation.js +8 -1
- package/dist/idl_codegen_kamino_vault/types/VaultAllocation.js.map +1 -1
- package/dist/utils/constants.d.ts +4 -0
- package/dist/utils/constants.d.ts.map +1 -1
- package/dist/utils/constants.js +5 -1
- package/dist/utils/constants.js.map +1 -1
- package/package.json +7 -2
- package/src/classes/action.ts +1 -4
- package/src/classes/manager.ts +28 -0
- package/src/classes/market.ts +65 -0
- package/src/classes/vault.ts +132 -29
- package/src/client_kamino_manager.ts +35 -4
- package/src/idl_codegen_kamino_vault/accounts/VaultState.ts +18 -2
- package/src/idl_codegen_kamino_vault/types/VaultAllocation.ts +10 -1
- package/src/utils/constants.ts +6 -0
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@kamino-finance/klend-sdk",
|
|
3
|
-
"version": "5.4.
|
|
3
|
+
"version": "5.4.2",
|
|
4
4
|
"description": "Typescript SDK for interacting with the Kamino Lending (klend) protocol",
|
|
5
5
|
"repository": {
|
|
6
6
|
"type": "git",
|
|
@@ -29,6 +29,7 @@
|
|
|
29
29
|
"docs": "typedoc",
|
|
30
30
|
"build": "rm -rf dist/; tsc",
|
|
31
31
|
"build:test": "yarn tsc --project ./tests/tsconfig.json",
|
|
32
|
+
"build:watch": "yarn tsc --watch",
|
|
32
33
|
"test": "npx ts-mocha tests/**/*.test.ts --exit",
|
|
33
34
|
"watch": "tsc --watch",
|
|
34
35
|
"coverage": "jest --coverage",
|
|
@@ -43,7 +44,9 @@
|
|
|
43
44
|
"start-validator": "solana-test-validator $(./deps/test-validator-params.sh)",
|
|
44
45
|
"start-validator-and-test": "yarn start-server-and-test 'yarn start-validator' http://127.0.0.1:8899/health test",
|
|
45
46
|
"dump-programs": "./deps/dump-from-mainnet.sh",
|
|
46
|
-
"kamino-manager": "tsx src/client_kamino_manager.ts"
|
|
47
|
+
"kamino-manager": "tsx src/client_kamino_manager.ts",
|
|
48
|
+
"dev": "concurrently \"yarn build:watch\" \"nodemon --watch dist -e js,ts --exec 'yarn dev:push-and-update'\"",
|
|
49
|
+
"dev:push-and-update": "yalc publish && cd examples && yalc update"
|
|
47
50
|
},
|
|
48
51
|
"dependencies": {
|
|
49
52
|
"@coral-xyz/anchor": "^0.28.0",
|
|
@@ -81,6 +84,7 @@
|
|
|
81
84
|
"anchor-client-gen": "^0.28.1",
|
|
82
85
|
"chai": "^4.3.7",
|
|
83
86
|
"chai-bignumber": "^3.1.0",
|
|
87
|
+
"concurrently": "^9.1.0",
|
|
84
88
|
"eslint": "^8.4.0",
|
|
85
89
|
"eslint-config-airbnb-base": "^15.0.0",
|
|
86
90
|
"eslint-config-airbnb-typescript": "^16.1.0",
|
|
@@ -90,6 +94,7 @@
|
|
|
90
94
|
"jest": "^27.4.3",
|
|
91
95
|
"jest-fetch-mock": "^3.0.3",
|
|
92
96
|
"mocha": "^10.2.0",
|
|
97
|
+
"nodemon": "^3.1.7",
|
|
93
98
|
"prettier": "^2.8.8",
|
|
94
99
|
"start-server-and-test": "^2.0.0",
|
|
95
100
|
"ts-jest": "^28.0.7",
|
package/src/classes/action.ts
CHANGED
|
@@ -58,6 +58,7 @@ import {
|
|
|
58
58
|
getAssociatedTokenAddress,
|
|
59
59
|
ScopeRefresh,
|
|
60
60
|
createAtasIdempotent,
|
|
61
|
+
POSITION_LIMIT,
|
|
61
62
|
} from '../utils';
|
|
62
63
|
import { KaminoMarket } from './market';
|
|
63
64
|
import { KaminoObligation } from './obligation';
|
|
@@ -69,10 +70,6 @@ import { VanillaObligation } from '../utils/ObligationType';
|
|
|
69
70
|
import { PROGRAM_ID } from '../lib';
|
|
70
71
|
import { U16_MAX } from '@kamino-finance/scope-sdk';
|
|
71
72
|
|
|
72
|
-
export const POSITION_LIMIT = 10;
|
|
73
|
-
export const BORROWS_LIMIT = 5;
|
|
74
|
-
export const DEPOSITS_LIMIT = 8;
|
|
75
|
-
|
|
76
73
|
const SOL_PADDING_FOR_INTEREST = new BN('1000000');
|
|
77
74
|
|
|
78
75
|
export type ActionType =
|
package/src/classes/manager.ts
CHANGED
|
@@ -17,6 +17,8 @@ import {
|
|
|
17
17
|
MarketOverview,
|
|
18
18
|
ReserveAllocationConfig,
|
|
19
19
|
ReserveOverview,
|
|
20
|
+
SimulatedVaultHoldingsWithEarnedInterest,
|
|
21
|
+
VaultFees,
|
|
20
22
|
VaultFeesPct,
|
|
21
23
|
VaultHolder,
|
|
22
24
|
VaultHoldings,
|
|
@@ -701,6 +703,32 @@ export class KaminoManager {
|
|
|
701
703
|
return this._vaultClient.getVaultCumulativeInterest(vaultState);
|
|
702
704
|
}
|
|
703
705
|
|
|
706
|
+
/**
|
|
707
|
+
* Simulate the current holdings of the vault and the earned interest
|
|
708
|
+
* @param vaultState the kamino vault state to get simulated holdings and earnings for
|
|
709
|
+
* @param vaultReserves optional; the state of the reserves in the vault allocation
|
|
710
|
+
* @returns a struct of simulated vault holdings and earned interest
|
|
711
|
+
*/
|
|
712
|
+
async calculateSimulatedHoldingsWithInterest(
|
|
713
|
+
vaultState: VaultState,
|
|
714
|
+
vaultReserves?: PubkeyHashMap<PublicKey, KaminoReserve>
|
|
715
|
+
): Promise<SimulatedVaultHoldingsWithEarnedInterest> {
|
|
716
|
+
return this._vaultClient.calculateSimulatedHoldingsWithInterest(vaultState, vaultReserves);
|
|
717
|
+
}
|
|
718
|
+
|
|
719
|
+
/**
|
|
720
|
+
* Simulate the current holdings and compute the fees that would be charged
|
|
721
|
+
* @param vaultState the kamino vault state to get simulated fees for
|
|
722
|
+
* @param simulatedCurrentHoldingsWithInterest optional; the simulated holdings and interest earned by the vault
|
|
723
|
+
* @returns a struct of simulated management and interest fees
|
|
724
|
+
*/
|
|
725
|
+
async calculateSimulatedFees(
|
|
726
|
+
vaultState: VaultState,
|
|
727
|
+
simulatedCurrentHoldingsWithInterest?: SimulatedVaultHoldingsWithEarnedInterest
|
|
728
|
+
): Promise<VaultFees> {
|
|
729
|
+
return this._vaultClient.calculateSimulatedFees(vaultState, simulatedCurrentHoldingsWithInterest);
|
|
730
|
+
}
|
|
731
|
+
|
|
704
732
|
/**
|
|
705
733
|
* This will load the onchain state for all the reserves that the vault has allocations for
|
|
706
734
|
* @param vaultState - the vault state to load reserves for
|
package/src/classes/market.ts
CHANGED
|
@@ -21,6 +21,7 @@ import {
|
|
|
21
21
|
PubkeyHashMap,
|
|
22
22
|
CandidatePrice,
|
|
23
23
|
PublicKeySet,
|
|
24
|
+
DEPOSITS_LIMIT,
|
|
24
25
|
} from '../utils';
|
|
25
26
|
import base58 from 'bs58';
|
|
26
27
|
import { BN } from '@coral-xyz/anchor';
|
|
@@ -727,6 +728,70 @@ export class KaminoMarket {
|
|
|
727
728
|
});
|
|
728
729
|
}
|
|
729
730
|
|
|
731
|
+
async getAllObligationsByDepositedReserve(reserve: PublicKey) {
|
|
732
|
+
const finalObligations: KaminoObligation[] = [];
|
|
733
|
+
for (let i = 0; i < DEPOSITS_LIMIT; i++) {
|
|
734
|
+
const [slot, obligations] = await Promise.all([
|
|
735
|
+
this.connection.getSlot(),
|
|
736
|
+
this.connection.getProgramAccounts(this.programId, {
|
|
737
|
+
filters: [
|
|
738
|
+
{
|
|
739
|
+
dataSize: Obligation.layout.span + 8,
|
|
740
|
+
},
|
|
741
|
+
{
|
|
742
|
+
memcmp: {
|
|
743
|
+
offset: 96 + 136 * i,
|
|
744
|
+
bytes: reserve.toBase58(),
|
|
745
|
+
},
|
|
746
|
+
},
|
|
747
|
+
{
|
|
748
|
+
memcmp: {
|
|
749
|
+
offset: 32,
|
|
750
|
+
bytes: this.address,
|
|
751
|
+
},
|
|
752
|
+
},
|
|
753
|
+
],
|
|
754
|
+
}),
|
|
755
|
+
]);
|
|
756
|
+
|
|
757
|
+
const collateralExchangeRates = new PubkeyHashMap<PublicKey, Decimal>();
|
|
758
|
+
const cumulativeBorrowRates = new PubkeyHashMap<PublicKey, Decimal>();
|
|
759
|
+
|
|
760
|
+
const obligationsBatch = obligations.map((obligation) => {
|
|
761
|
+
if (obligation.account === null) {
|
|
762
|
+
throw new Error('Invalid account');
|
|
763
|
+
}
|
|
764
|
+
if (!obligation.account.owner.equals(this.programId)) {
|
|
765
|
+
throw new Error("account doesn't belong to this program");
|
|
766
|
+
}
|
|
767
|
+
|
|
768
|
+
const obligationAccount = Obligation.decode(obligation.account.data);
|
|
769
|
+
|
|
770
|
+
if (!obligationAccount) {
|
|
771
|
+
throw Error('Could not parse obligation.');
|
|
772
|
+
}
|
|
773
|
+
|
|
774
|
+
KaminoObligation.addRatesForObligation(
|
|
775
|
+
this,
|
|
776
|
+
obligationAccount,
|
|
777
|
+
collateralExchangeRates,
|
|
778
|
+
cumulativeBorrowRates,
|
|
779
|
+
slot
|
|
780
|
+
);
|
|
781
|
+
|
|
782
|
+
return new KaminoObligation(
|
|
783
|
+
this,
|
|
784
|
+
obligation.pubkey,
|
|
785
|
+
obligationAccount,
|
|
786
|
+
collateralExchangeRates,
|
|
787
|
+
cumulativeBorrowRates
|
|
788
|
+
);
|
|
789
|
+
});
|
|
790
|
+
finalObligations.push(...obligationsBatch);
|
|
791
|
+
}
|
|
792
|
+
return finalObligations;
|
|
793
|
+
}
|
|
794
|
+
|
|
730
795
|
async getAllUserObligations(user: PublicKey) {
|
|
731
796
|
const [currentSlot, obligations] = await Promise.all([
|
|
732
797
|
this.connection.getSlot(),
|
package/src/classes/vault.ts
CHANGED
|
@@ -58,7 +58,7 @@ import { withdraw } from '../idl_codegen_kamino_vault/instructions';
|
|
|
58
58
|
import { PROGRAM_ID } from '../idl_codegen/programId';
|
|
59
59
|
import { DEFAULT_RECENT_SLOT_DURATION_MS, ReserveWithAddress } from './reserve';
|
|
60
60
|
import { Fraction } from './fraction';
|
|
61
|
-
import { createAtasIdempotent, lendingMarketAuthPda, PublicKeySet } from '../utils';
|
|
61
|
+
import { createAtasIdempotent, lendingMarketAuthPda, PublicKeySet, SECONDS_PER_YEAR } from '../utils';
|
|
62
62
|
import bs58 from 'bs58';
|
|
63
63
|
import { getAccountOwner, getProgramAccounts } from '../utils/rpc';
|
|
64
64
|
import {
|
|
@@ -69,6 +69,7 @@ import {
|
|
|
69
69
|
UpdateVaultConfigIxs,
|
|
70
70
|
} from './types';
|
|
71
71
|
import { collToLamportsDecimal } from '@kamino-finance/kliquidity-sdk';
|
|
72
|
+
import { FullBPSDecimal } from '@kamino-finance/kliquidity-sdk/dist/utils/CreationParameters';
|
|
72
73
|
|
|
73
74
|
export const kaminoVaultId = new PublicKey('kvauTFR8qm1dhniz6pYuBZkuene3Hfrs1VQhVRgCNrr');
|
|
74
75
|
export const kaminoVaultStagingId = new PublicKey('STkvh7ostar39Fwr4uZKASs1RNNuYMFMTsE77FiRsL2');
|
|
@@ -675,8 +676,8 @@ export class KaminoVaultClient {
|
|
|
675
676
|
const kaminoVault = new KaminoVault(vault.address, vaultState, vault.programId);
|
|
676
677
|
|
|
677
678
|
// if the vault has allocations withdraw otherwise wtihdraw from available ix
|
|
678
|
-
const vaultAllocation = vaultState.vaultAllocationStrategy.find(
|
|
679
|
-
|
|
679
|
+
const vaultAllocation = vaultState.vaultAllocationStrategy.find((allocation) =>
|
|
680
|
+
allocation.reserve.equals(PublicKey.default)
|
|
680
681
|
);
|
|
681
682
|
|
|
682
683
|
if (vaultAllocation) {
|
|
@@ -734,18 +735,25 @@ export class KaminoVaultClient {
|
|
|
734
735
|
},
|
|
735
736
|
]);
|
|
736
737
|
|
|
737
|
-
const
|
|
738
|
+
const shareLamportsToWithdraw = collToLamportsDecimal(shareAmount, vaultState.sharesMintDecimals.toNumber());
|
|
739
|
+
const tokensPerShare = await this.getTokensPerShareSingleVault(vault, slot);
|
|
740
|
+
const sharesPerToken = new Decimal(1).div(tokensPerShare);
|
|
741
|
+
const tokensToWithdraw = shareLamportsToWithdraw.mul(tokensPerShare);
|
|
738
742
|
let tokenLeftToWithdraw = tokensToWithdraw;
|
|
743
|
+
const availableTokens = new Decimal(vaultState.tokenAvailable.toString());
|
|
744
|
+
tokenLeftToWithdraw = tokenLeftToWithdraw.sub(availableTokens);
|
|
739
745
|
|
|
740
|
-
|
|
746
|
+
type ReserveWithTokensToWithdraw = { reserve: PublicKey; shares: Decimal };
|
|
741
747
|
|
|
742
|
-
const
|
|
743
|
-
|
|
744
|
-
amountToWithdraw.push(new Decimal(vaultState.tokenAvailable.toString()));
|
|
748
|
+
const reserveWithSharesAmountToWithdraw: ReserveWithTokensToWithdraw[] = [];
|
|
749
|
+
let isFirstWithdraw = true;
|
|
745
750
|
|
|
746
751
|
if (tokenLeftToWithdraw.lte(0)) {
|
|
747
752
|
// Availabe enough to withdraw all - using first reserve as it does not matter
|
|
748
|
-
|
|
753
|
+
reserveWithSharesAmountToWithdraw.push({
|
|
754
|
+
reserve: vaultState.vaultAllocationStrategy[0].reserve,
|
|
755
|
+
shares: shareLamportsToWithdraw,
|
|
756
|
+
});
|
|
749
757
|
} else {
|
|
750
758
|
// Get decreasing order sorted available liquidity to withdraw from each reserve allocated to
|
|
751
759
|
const reserveAllocationAvailableLiquidityToWithdraw = await this.getReserveAllocationAvailableLiquidityToWithdraw(
|
|
@@ -754,15 +762,22 @@ export class KaminoVaultClient {
|
|
|
754
762
|
vaultReservesState
|
|
755
763
|
);
|
|
756
764
|
// sort
|
|
757
|
-
const reserveAllocationAvailableLiquidityToWithdrawSorted =
|
|
758
|
-
|
|
759
|
-
);
|
|
765
|
+
const reserveAllocationAvailableLiquidityToWithdrawSorted = [
|
|
766
|
+
...reserveAllocationAvailableLiquidityToWithdraw.entries(),
|
|
767
|
+
].sort((a, b) => b[1].sub(a[1]).toNumber());
|
|
760
768
|
|
|
761
|
-
reserveAllocationAvailableLiquidityToWithdrawSorted.forEach((availableLiquidityToWithdraw,
|
|
769
|
+
reserveAllocationAvailableLiquidityToWithdrawSorted.forEach(([key, availableLiquidityToWithdraw], _) => {
|
|
762
770
|
if (tokenLeftToWithdraw.gt(0)) {
|
|
763
|
-
|
|
764
|
-
|
|
765
|
-
|
|
771
|
+
let tokensToWithdrawFromReserve = Decimal.min(tokenLeftToWithdraw, availableLiquidityToWithdraw);
|
|
772
|
+
if (isFirstWithdraw) {
|
|
773
|
+
tokensToWithdrawFromReserve = tokensToWithdrawFromReserve.add(availableTokens);
|
|
774
|
+
isFirstWithdraw = false;
|
|
775
|
+
}
|
|
776
|
+
// round up to the nearest integer the shares to withdraw
|
|
777
|
+
const sharesToWithdrawFromReserve = tokensToWithdrawFromReserve.mul(sharesPerToken).ceil();
|
|
778
|
+
reserveWithSharesAmountToWithdraw.push({ reserve: key, shares: sharesToWithdrawFromReserve });
|
|
779
|
+
|
|
780
|
+
tokenLeftToWithdraw = tokenLeftToWithdraw.sub(tokensToWithdrawFromReserve);
|
|
766
781
|
}
|
|
767
782
|
});
|
|
768
783
|
}
|
|
@@ -770,26 +785,35 @@ export class KaminoVaultClient {
|
|
|
770
785
|
const withdrawIxns: TransactionInstruction[] = [];
|
|
771
786
|
withdrawIxns.push(createAtaIx);
|
|
772
787
|
|
|
773
|
-
|
|
774
|
-
|
|
788
|
+
// let sharesLeftToWithdraw = shareAmount;
|
|
789
|
+
for (let reserveIndex = 0; reserveIndex < reserveWithSharesAmountToWithdraw.length; reserveIndex++) {
|
|
790
|
+
const reserveWithTokens = reserveWithSharesAmountToWithdraw[reserveIndex];
|
|
791
|
+
const reserveState = vaultReservesState.get(reserveWithTokens.reserve);
|
|
775
792
|
if (reserveState === undefined) {
|
|
776
|
-
throw new Error(`Reserve ${reserve.toBase58()} not found in vault reserves map`);
|
|
793
|
+
throw new Error(`Reserve ${reserveWithTokens.reserve.toBase58()} not found in vault reserves map`);
|
|
777
794
|
}
|
|
778
|
-
|
|
779
795
|
const marketAddress = reserveState.state.lendingMarket;
|
|
796
|
+
|
|
797
|
+
const isLastWithdraw = reserveIndex === reserveWithSharesAmountToWithdraw.length - 1;
|
|
798
|
+
// if it is not last withdraw it means that we can pass all shares as we are withdrawing everything from that reserve
|
|
799
|
+
let sharesToWithdraw = shareAmount;
|
|
800
|
+
if (isLastWithdraw) {
|
|
801
|
+
sharesToWithdraw = reserveWithTokens.shares;
|
|
802
|
+
}
|
|
803
|
+
|
|
780
804
|
const withdrawFromReserveIx = this.withdrawIxn(
|
|
781
805
|
user,
|
|
782
806
|
vault,
|
|
783
807
|
vaultState,
|
|
784
808
|
marketAddress,
|
|
785
|
-
{ address: reserve, state: reserveState.state },
|
|
809
|
+
{ address: reserveWithTokens.reserve, state: reserveState.state },
|
|
786
810
|
userSharesAta,
|
|
787
811
|
userTokenAta,
|
|
788
|
-
|
|
812
|
+
sharesToWithdraw,
|
|
789
813
|
vaultReservesState
|
|
790
814
|
);
|
|
791
815
|
withdrawIxns.push(withdrawFromReserveIx);
|
|
792
|
-
}
|
|
816
|
+
}
|
|
793
817
|
|
|
794
818
|
return withdrawIxns;
|
|
795
819
|
}
|
|
@@ -1483,18 +1507,23 @@ export class KaminoVaultClient {
|
|
|
1483
1507
|
|
|
1484
1508
|
const reserveAllocationAvailableLiquidityToWithdraw = new PubkeyHashMap<PublicKey, Decimal>();
|
|
1485
1509
|
vaultState.vaultAllocationStrategy.forEach((allocationStrategy) => {
|
|
1510
|
+
if (allocationStrategy.reserve.equals(PublicKey.default)) {
|
|
1511
|
+
return;
|
|
1512
|
+
}
|
|
1486
1513
|
const reserve = reserves.get(allocationStrategy.reserve);
|
|
1487
1514
|
if (reserve === undefined) {
|
|
1488
1515
|
throw new Error(`Reserve ${allocationStrategy.reserve.toBase58()} not found`);
|
|
1489
1516
|
}
|
|
1490
|
-
|
|
1491
|
-
|
|
1492
|
-
|
|
1517
|
+
let referralFeeBps = 0;
|
|
1518
|
+
const denominator = reserve.state.config.protocolTakeRatePct / 100;
|
|
1519
|
+
if (denominator > 0) {
|
|
1520
|
+
referralFeeBps = new Fraction(reserve.state.liquidity.absoluteReferralRateSf)
|
|
1493
1521
|
.toDecimal()
|
|
1494
|
-
.div(
|
|
1522
|
+
.div(denominator)
|
|
1495
1523
|
.floor()
|
|
1496
|
-
.toNumber()
|
|
1497
|
-
|
|
1524
|
+
.toNumber();
|
|
1525
|
+
}
|
|
1526
|
+
const reserveCollExchangeRate = reserve.getEstimatedCollateralExchangeRate(slot, referralFeeBps);
|
|
1498
1527
|
const reserveAllocationLiquidityAmount = new Decimal(allocationStrategy.ctokenAllocation.toString()).div(
|
|
1499
1528
|
reserveCollExchangeRate
|
|
1500
1529
|
);
|
|
@@ -1922,6 +1951,70 @@ export class KaminoVaultClient {
|
|
|
1922
1951
|
const netYieldLamports = new Fraction(vaultState.cumulativeEarnedInterestSf).toDecimal();
|
|
1923
1952
|
return lamportsToDecimal(netYieldLamports, vaultState.tokenMintDecimals.toString());
|
|
1924
1953
|
}
|
|
1954
|
+
|
|
1955
|
+
/**
|
|
1956
|
+
* Simulate the current holdings of the vault and the earned interest
|
|
1957
|
+
* @param vaultState the kamino vault state to get simulated holdings and earnings for
|
|
1958
|
+
* @param vaultReserves optional; the state of the reserves in the vault allocation
|
|
1959
|
+
* @returns a struct of simulated vault holdings and earned interest
|
|
1960
|
+
*/
|
|
1961
|
+
async calculateSimulatedHoldingsWithInterest(
|
|
1962
|
+
vaultState: VaultState,
|
|
1963
|
+
vaultReserves?: PubkeyHashMap<PublicKey, KaminoReserve>
|
|
1964
|
+
): Promise<SimulatedVaultHoldingsWithEarnedInterest> {
|
|
1965
|
+
const latestUpdateTs = vaultState.lastFeeChargeTimestamp.toNumber();
|
|
1966
|
+
const lastUpdateSlot = latestUpdateTs / this.recentSlotDurationMs;
|
|
1967
|
+
|
|
1968
|
+
const currentSlot = await this._connection.getSlot('confirmed');
|
|
1969
|
+
|
|
1970
|
+
const lastUpdateHoldingsPromise = this.getVaultHoldings(vaultState, lastUpdateSlot, vaultReserves);
|
|
1971
|
+
const currentHoldingsPromise = this.getVaultHoldings(vaultState, currentSlot, vaultReserves);
|
|
1972
|
+
const [lastUpdateHoldings, currentHoldings] = await Promise.all([
|
|
1973
|
+
lastUpdateHoldingsPromise,
|
|
1974
|
+
currentHoldingsPromise,
|
|
1975
|
+
]);
|
|
1976
|
+
|
|
1977
|
+
const earnedInterest = currentHoldings.total.sub(lastUpdateHoldings.total);
|
|
1978
|
+
|
|
1979
|
+
return {
|
|
1980
|
+
holdings: currentHoldings,
|
|
1981
|
+
earnedInterest: earnedInterest,
|
|
1982
|
+
};
|
|
1983
|
+
}
|
|
1984
|
+
|
|
1985
|
+
/**
|
|
1986
|
+
* Simulate the current holdings and compute the fees that would be charged
|
|
1987
|
+
* @param vaultState the kamino vault state to get simulated fees for
|
|
1988
|
+
* @param simulatedCurrentHoldingsWithInterest optional; the simulated holdings and interest earned by the vault
|
|
1989
|
+
* @returns a struct of simulated management and interest fees
|
|
1990
|
+
*/
|
|
1991
|
+
async calculateSimulatedFees(
|
|
1992
|
+
vaultState: VaultState,
|
|
1993
|
+
simulatedCurrentHoldingsWithInterest?: SimulatedVaultHoldingsWithEarnedInterest
|
|
1994
|
+
): Promise<VaultFees> {
|
|
1995
|
+
const timestampNow = new Date().getTime();
|
|
1996
|
+
const timestampLastUpdate = vaultState.lastFeeChargeTimestamp.toNumber();
|
|
1997
|
+
const timeElapsed = timestampNow - timestampLastUpdate;
|
|
1998
|
+
|
|
1999
|
+
const simulatedCurrentHoldings = simulatedCurrentHoldingsWithInterest
|
|
2000
|
+
? simulatedCurrentHoldingsWithInterest
|
|
2001
|
+
: await this.calculateSimulatedHoldingsWithInterest(vaultState);
|
|
2002
|
+
|
|
2003
|
+
const performanceFee = simulatedCurrentHoldings.earnedInterest.mul(
|
|
2004
|
+
new Decimal(vaultState.performanceFeeBps.toString()).div(FullBPSDecimal)
|
|
2005
|
+
);
|
|
2006
|
+
|
|
2007
|
+
const managementFeeFactor = new Decimal(timeElapsed)
|
|
2008
|
+
.mul(new Decimal(vaultState.managementFeeBps.toString()))
|
|
2009
|
+
.div(new Decimal(SECONDS_PER_YEAR));
|
|
2010
|
+
const prevAUM = new Fraction(vaultState.prevAumSf).toDecimal();
|
|
2011
|
+
const mgmtFee = prevAUM.mul(managementFeeFactor);
|
|
2012
|
+
|
|
2013
|
+
return {
|
|
2014
|
+
managementFee: mgmtFee,
|
|
2015
|
+
performanceFee: performanceFee,
|
|
2016
|
+
};
|
|
2017
|
+
}
|
|
1925
2018
|
} // KaminoVaultClient
|
|
1926
2019
|
|
|
1927
2020
|
export class KaminoVault {
|
|
@@ -2042,6 +2135,11 @@ export type VaultHoldings = {
|
|
|
2042
2135
|
total: Decimal;
|
|
2043
2136
|
};
|
|
2044
2137
|
|
|
2138
|
+
export type SimulatedVaultHoldingsWithEarnedInterest = {
|
|
2139
|
+
holdings: VaultHoldings;
|
|
2140
|
+
earnedInterest: Decimal;
|
|
2141
|
+
};
|
|
2142
|
+
|
|
2045
2143
|
export type VaultHoldingsWithUSDValue = {
|
|
2046
2144
|
holdings: VaultHoldings;
|
|
2047
2145
|
availableUSD: Decimal;
|
|
@@ -2091,3 +2189,8 @@ export type VaultFeesPct = {
|
|
|
2091
2189
|
managementFeePct: Decimal;
|
|
2092
2190
|
performanceFeePct: Decimal;
|
|
2093
2191
|
};
|
|
2192
|
+
|
|
2193
|
+
export type VaultFees = {
|
|
2194
|
+
managementFee: Decimal;
|
|
2195
|
+
performanceFee: Decimal;
|
|
2196
|
+
};
|
|
@@ -649,7 +649,7 @@ async function main() {
|
|
|
649
649
|
const kaminoVault = new KaminoVault(vaultAddress, undefined, env.kVaultProgramId);
|
|
650
650
|
const instructions = await kaminoManager.depositToVaultIxs(env.payer.publicKey, kaminoVault, amount);
|
|
651
651
|
|
|
652
|
-
const depositSig = await processTxn(env.client, env.payer, instructions, mode, 2500, []);
|
|
652
|
+
const depositSig = await processTxn(env.client, env.payer, instructions, mode, 2500, [], 800_000);
|
|
653
653
|
|
|
654
654
|
mode === 'execute' && console.log('User deposit:', depositSig);
|
|
655
655
|
});
|
|
@@ -675,14 +675,14 @@ async function main() {
|
|
|
675
675
|
const kaminoManager = new KaminoManager(env.connection, env.kLendProgramId, env.kVaultProgramId);
|
|
676
676
|
|
|
677
677
|
const kaminoVault = new KaminoVault(vaultAddress, undefined, env.kVaultProgramId);
|
|
678
|
-
const
|
|
678
|
+
const withdrawIxs = await kaminoManager.withdrawFromVaultIxs(
|
|
679
679
|
env.payer.publicKey,
|
|
680
680
|
kaminoVault,
|
|
681
681
|
new Decimal(amount),
|
|
682
682
|
await env.connection.getSlot('confirmed')
|
|
683
683
|
);
|
|
684
684
|
|
|
685
|
-
const depositSig = await processTxn(env.client, env.payer,
|
|
685
|
+
const depositSig = await processTxn(env.client, env.payer, withdrawIxs, mode, 2500, [], 800_000);
|
|
686
686
|
|
|
687
687
|
mode === 'execute' && console.log('User withdraw:', depositSig);
|
|
688
688
|
});
|
|
@@ -754,7 +754,7 @@ async function main() {
|
|
|
754
754
|
kaminoVault,
|
|
755
755
|
reserveWithAddress
|
|
756
756
|
);
|
|
757
|
-
const investReserveSig = await processTxn(env.client, env.payer, instructions, mode, 2500, [],
|
|
757
|
+
const investReserveSig = await processTxn(env.client, env.payer, instructions, mode, 2500, [], 400_000);
|
|
758
758
|
|
|
759
759
|
mode === 'execute' && console.log('Reserve invested:', investReserveSig);
|
|
760
760
|
});
|
|
@@ -926,6 +926,37 @@ async function main() {
|
|
|
926
926
|
console.log('all vaults', allVaults);
|
|
927
927
|
});
|
|
928
928
|
|
|
929
|
+
commands.command('get-all-vaults-pks').action(async () => {
|
|
930
|
+
const env = initializeClient(false, false);
|
|
931
|
+
const kaminoManager = new KaminoManager(env.connection, env.kLendProgramId, env.kVaultProgramId);
|
|
932
|
+
|
|
933
|
+
const allVaults = await kaminoManager.getAllVaults();
|
|
934
|
+
console.log(
|
|
935
|
+
'all vaults',
|
|
936
|
+
allVaults.map((vault) => vault.address.toBase58())
|
|
937
|
+
);
|
|
938
|
+
});
|
|
939
|
+
|
|
940
|
+
commands
|
|
941
|
+
.command('get-simulated-interest-and-fees')
|
|
942
|
+
.requiredOption('--vault <string>', 'Vault address')
|
|
943
|
+
.action(async ({ vault }) => {
|
|
944
|
+
const env = initializeClient(false, false);
|
|
945
|
+
|
|
946
|
+
const vaultAddress = new PublicKey(vault);
|
|
947
|
+
|
|
948
|
+
const kaminoManager = new KaminoManager(env.connection, env.kLendProgramId, env.kVaultProgramId);
|
|
949
|
+
const vaultState = await new KaminoVault(vaultAddress, undefined, env.kVaultProgramId).getState(env.connection);
|
|
950
|
+
|
|
951
|
+
const simulatedHoldings = await kaminoManager.calculateSimulatedHoldingsWithInterest(vaultState);
|
|
952
|
+
|
|
953
|
+
console.log('Simulated holdings with interest', simulatedHoldings);
|
|
954
|
+
|
|
955
|
+
const simulatedFees = await kaminoManager.calculateSimulatedFees(vaultState, simulatedHoldings);
|
|
956
|
+
|
|
957
|
+
console.log('Simulated fees', simulatedFees);
|
|
958
|
+
});
|
|
959
|
+
|
|
929
960
|
commands
|
|
930
961
|
.command('download-lending-market-config')
|
|
931
962
|
.requiredOption('--lending-market <string>', 'Lending Market Address')
|
|
@@ -36,6 +36,8 @@ export interface VaultStateFields {
|
|
|
36
36
|
name: Array<number>
|
|
37
37
|
vaultLookupTable: PublicKey
|
|
38
38
|
vaultFarm: PublicKey
|
|
39
|
+
creationTimestamp: BN
|
|
40
|
+
padding1: BN
|
|
39
41
|
padding2: Array<BN>
|
|
40
42
|
}
|
|
41
43
|
|
|
@@ -71,6 +73,8 @@ export interface VaultStateJSON {
|
|
|
71
73
|
name: Array<number>
|
|
72
74
|
vaultLookupTable: string
|
|
73
75
|
vaultFarm: string
|
|
76
|
+
creationTimestamp: string
|
|
77
|
+
padding1: string
|
|
74
78
|
padding2: Array<string>
|
|
75
79
|
}
|
|
76
80
|
|
|
@@ -106,6 +110,8 @@ export class VaultState {
|
|
|
106
110
|
readonly name: Array<number>
|
|
107
111
|
readonly vaultLookupTable: PublicKey
|
|
108
112
|
readonly vaultFarm: PublicKey
|
|
113
|
+
readonly creationTimestamp: BN
|
|
114
|
+
readonly padding1: BN
|
|
109
115
|
readonly padding2: Array<BN>
|
|
110
116
|
|
|
111
117
|
static readonly discriminator = Buffer.from([
|
|
@@ -144,7 +150,9 @@ export class VaultState {
|
|
|
144
150
|
borsh.array(borsh.u8(), 40, "name"),
|
|
145
151
|
borsh.publicKey("vaultLookupTable"),
|
|
146
152
|
borsh.publicKey("vaultFarm"),
|
|
147
|
-
borsh.
|
|
153
|
+
borsh.u64("creationTimestamp"),
|
|
154
|
+
borsh.u64("padding1"),
|
|
155
|
+
borsh.array(borsh.u128(), 244, "padding2"),
|
|
148
156
|
])
|
|
149
157
|
|
|
150
158
|
constructor(fields: VaultStateFields) {
|
|
@@ -181,6 +189,8 @@ export class VaultState {
|
|
|
181
189
|
this.name = fields.name
|
|
182
190
|
this.vaultLookupTable = fields.vaultLookupTable
|
|
183
191
|
this.vaultFarm = fields.vaultFarm
|
|
192
|
+
this.creationTimestamp = fields.creationTimestamp
|
|
193
|
+
this.padding1 = fields.padding1
|
|
184
194
|
this.padding2 = fields.padding2
|
|
185
195
|
}
|
|
186
196
|
|
|
@@ -221,7 +231,7 @@ export class VaultState {
|
|
|
221
231
|
}
|
|
222
232
|
|
|
223
233
|
static decode(data: Buffer): VaultState {
|
|
224
|
-
if (!data.slice(0, 8)
|
|
234
|
+
if (!VaultState.discriminator.equals(data.slice(0, 8))) {
|
|
225
235
|
throw new Error("invalid account discriminator")
|
|
226
236
|
}
|
|
227
237
|
|
|
@@ -263,6 +273,8 @@ export class VaultState {
|
|
|
263
273
|
name: dec.name,
|
|
264
274
|
vaultLookupTable: dec.vaultLookupTable,
|
|
265
275
|
vaultFarm: dec.vaultFarm,
|
|
276
|
+
creationTimestamp: dec.creationTimestamp,
|
|
277
|
+
padding1: dec.padding1,
|
|
266
278
|
padding2: dec.padding2,
|
|
267
279
|
})
|
|
268
280
|
}
|
|
@@ -302,6 +314,8 @@ export class VaultState {
|
|
|
302
314
|
name: this.name,
|
|
303
315
|
vaultLookupTable: this.vaultLookupTable.toString(),
|
|
304
316
|
vaultFarm: this.vaultFarm.toString(),
|
|
317
|
+
creationTimestamp: this.creationTimestamp.toString(),
|
|
318
|
+
padding1: this.padding1.toString(),
|
|
305
319
|
padding2: this.padding2.map((item) => item.toString()),
|
|
306
320
|
}
|
|
307
321
|
}
|
|
@@ -341,6 +355,8 @@ export class VaultState {
|
|
|
341
355
|
name: obj.name,
|
|
342
356
|
vaultLookupTable: new PublicKey(obj.vaultLookupTable),
|
|
343
357
|
vaultFarm: new PublicKey(obj.vaultFarm),
|
|
358
|
+
creationTimestamp: new BN(obj.creationTimestamp),
|
|
359
|
+
padding1: new BN(obj.padding1),
|
|
344
360
|
padding2: obj.padding2.map((item) => new BN(item)),
|
|
345
361
|
})
|
|
346
362
|
}
|
|
@@ -9,6 +9,7 @@ export interface VaultAllocationFields {
|
|
|
9
9
|
targetAllocationWeight: BN
|
|
10
10
|
/** Maximum token invested in this reserve */
|
|
11
11
|
tokenAllocationCap: BN
|
|
12
|
+
ctokenVaultBump: BN
|
|
12
13
|
configPadding: Array<BN>
|
|
13
14
|
ctokenAllocation: BN
|
|
14
15
|
lastInvestSlot: BN
|
|
@@ -22,6 +23,7 @@ export interface VaultAllocationJSON {
|
|
|
22
23
|
targetAllocationWeight: string
|
|
23
24
|
/** Maximum token invested in this reserve */
|
|
24
25
|
tokenAllocationCap: string
|
|
26
|
+
ctokenVaultBump: string
|
|
25
27
|
configPadding: Array<string>
|
|
26
28
|
ctokenAllocation: string
|
|
27
29
|
lastInvestSlot: string
|
|
@@ -35,6 +37,7 @@ export class VaultAllocation {
|
|
|
35
37
|
readonly targetAllocationWeight: BN
|
|
36
38
|
/** Maximum token invested in this reserve */
|
|
37
39
|
readonly tokenAllocationCap: BN
|
|
40
|
+
readonly ctokenVaultBump: BN
|
|
38
41
|
readonly configPadding: Array<BN>
|
|
39
42
|
readonly ctokenAllocation: BN
|
|
40
43
|
readonly lastInvestSlot: BN
|
|
@@ -46,6 +49,7 @@ export class VaultAllocation {
|
|
|
46
49
|
this.ctokenVault = fields.ctokenVault
|
|
47
50
|
this.targetAllocationWeight = fields.targetAllocationWeight
|
|
48
51
|
this.tokenAllocationCap = fields.tokenAllocationCap
|
|
52
|
+
this.ctokenVaultBump = fields.ctokenVaultBump
|
|
49
53
|
this.configPadding = fields.configPadding
|
|
50
54
|
this.ctokenAllocation = fields.ctokenAllocation
|
|
51
55
|
this.lastInvestSlot = fields.lastInvestSlot
|
|
@@ -60,7 +64,8 @@ export class VaultAllocation {
|
|
|
60
64
|
borsh.publicKey("ctokenVault"),
|
|
61
65
|
borsh.u64("targetAllocationWeight"),
|
|
62
66
|
borsh.u64("tokenAllocationCap"),
|
|
63
|
-
borsh.
|
|
67
|
+
borsh.u64("ctokenVaultBump"),
|
|
68
|
+
borsh.array(borsh.u64(), 127, "configPadding"),
|
|
64
69
|
borsh.u64("ctokenAllocation"),
|
|
65
70
|
borsh.u64("lastInvestSlot"),
|
|
66
71
|
borsh.u128("tokenTargetAllocationSf"),
|
|
@@ -77,6 +82,7 @@ export class VaultAllocation {
|
|
|
77
82
|
ctokenVault: obj.ctokenVault,
|
|
78
83
|
targetAllocationWeight: obj.targetAllocationWeight,
|
|
79
84
|
tokenAllocationCap: obj.tokenAllocationCap,
|
|
85
|
+
ctokenVaultBump: obj.ctokenVaultBump,
|
|
80
86
|
configPadding: obj.configPadding,
|
|
81
87
|
ctokenAllocation: obj.ctokenAllocation,
|
|
82
88
|
lastInvestSlot: obj.lastInvestSlot,
|
|
@@ -91,6 +97,7 @@ export class VaultAllocation {
|
|
|
91
97
|
ctokenVault: fields.ctokenVault,
|
|
92
98
|
targetAllocationWeight: fields.targetAllocationWeight,
|
|
93
99
|
tokenAllocationCap: fields.tokenAllocationCap,
|
|
100
|
+
ctokenVaultBump: fields.ctokenVaultBump,
|
|
94
101
|
configPadding: fields.configPadding,
|
|
95
102
|
ctokenAllocation: fields.ctokenAllocation,
|
|
96
103
|
lastInvestSlot: fields.lastInvestSlot,
|
|
@@ -105,6 +112,7 @@ export class VaultAllocation {
|
|
|
105
112
|
ctokenVault: this.ctokenVault.toString(),
|
|
106
113
|
targetAllocationWeight: this.targetAllocationWeight.toString(),
|
|
107
114
|
tokenAllocationCap: this.tokenAllocationCap.toString(),
|
|
115
|
+
ctokenVaultBump: this.ctokenVaultBump.toString(),
|
|
108
116
|
configPadding: this.configPadding.map((item) => item.toString()),
|
|
109
117
|
ctokenAllocation: this.ctokenAllocation.toString(),
|
|
110
118
|
lastInvestSlot: this.lastInvestSlot.toString(),
|
|
@@ -119,6 +127,7 @@ export class VaultAllocation {
|
|
|
119
127
|
ctokenVault: new PublicKey(obj.ctokenVault),
|
|
120
128
|
targetAllocationWeight: new BN(obj.targetAllocationWeight),
|
|
121
129
|
tokenAllocationCap: new BN(obj.tokenAllocationCap),
|
|
130
|
+
ctokenVaultBump: new BN(obj.ctokenVaultBump),
|
|
122
131
|
configPadding: obj.configPadding.map((item) => new BN(item)),
|
|
123
132
|
ctokenAllocation: new BN(obj.ctokenAllocation),
|
|
124
133
|
lastInvestSlot: new BN(obj.lastInvestSlot),
|
package/src/utils/constants.ts
CHANGED
|
@@ -8,6 +8,8 @@ export const U64_MAX = '18446744073709551615';
|
|
|
8
8
|
const INITIAL_COLLATERAL_RATIO = 1;
|
|
9
9
|
export const INITIAL_COLLATERAL_RATE = new Decimal(INITIAL_COLLATERAL_RATIO);
|
|
10
10
|
|
|
11
|
+
export const SECONDS_PER_YEAR = 365.242_199 * 24.0 * 60.0 * 60.0;
|
|
12
|
+
|
|
11
13
|
export type ENV = 'mainnet-beta' | 'devnet' | 'localnet';
|
|
12
14
|
|
|
13
15
|
export function isENV(value: any): value is ENV {
|
|
@@ -81,3 +83,7 @@ export const SOL_DECIMALS = 9;
|
|
|
81
83
|
export const USDC_MAINNET_MINT = new PublicKey('EPjFWdd5AufqSSqeM2qN1xzybapC8G4wEGGkZwyTDt1v');
|
|
82
84
|
|
|
83
85
|
export const MAINNET_BETA_CHAIN_ID = 101;
|
|
86
|
+
|
|
87
|
+
export const POSITION_LIMIT = 10;
|
|
88
|
+
export const BORROWS_LIMIT = 5;
|
|
89
|
+
export const DEPOSITS_LIMIT = 8;
|