Skip to content

Commit d0ca316

Browse files
committed
add examples for the different vault creation levels
1 parent 60f5ea1 commit d0ca316

File tree

6 files changed

+334
-1
lines changed

6 files changed

+334
-1
lines changed
Lines changed: 154 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,154 @@
1+
import { getConnectionPool } from '../utils/connection';
2+
import { getKeypair } from '../utils/keypair';
3+
import { USDC_MINT } from '../utils/constants';
4+
import Decimal from 'decimal.js/decimal';
5+
import {
6+
AssetReserveConfig,
7+
getAssociatedTokenAddress,
8+
getDefaultConfigParams,
9+
getMedianSlotDurationInMsFromLastEpochs,
10+
KaminoManager,
11+
KaminoVault,
12+
KaminoVaultConfig,
13+
LendingMarket,
14+
Reserve,
15+
ReserveAllocationConfig,
16+
sleep,
17+
} from '@kamino-finance/klend-sdk';
18+
import { address } from '@solana/kit';
19+
import { TOKEN_PROGRAM_ADDRESS } from '@solana-program/token';
20+
import { sendAndConfirmTx } from '../utils/tx';
21+
22+
/**
23+
* Option: Create your own lending market + add a reserve, then create a vault on top
24+
* - You are the market admin AND the vault admin.
25+
* - This is advanced: you must provide valid oracle configuration.
26+
*
27+
* Required env vars:
28+
* - `PYTH_PRICE` = Pyth price account for the asset you add (base58 address)
29+
*
30+
* Optional:
31+
* - `EXECUTE=true` to actually send transactions (default is dry-run).
32+
*/
33+
(async () => {
34+
const c = getConnectionPool();
35+
const admin = await getKeypair();
36+
37+
const execute = (process.env.EXECUTE ?? 'false').toLowerCase() === 'true';
38+
const pythPriceStr = process.env.PYTH_PRICE;
39+
if (!pythPriceStr) {
40+
throw new Error('Missing env var PYTH_PRICE (Pyth price account address)');
41+
}
42+
43+
const slotDuration = await getMedianSlotDurationInMsFromLastEpochs();
44+
const kaminoManager = new KaminoManager(c.rpc, slotDuration);
45+
46+
// 1) Create a new lending market
47+
const { market, ixs: createMarketIxs } = await kaminoManager.createMarketIxs({ admin });
48+
console.log('New market:', market.address);
49+
50+
if (execute) {
51+
await sendAndConfirmTx(c, admin, createMarketIxs, [market], [], 'OptionCreateMarketReserve-CreateMarket');
52+
} else {
53+
console.log('[dry-run] Would send createMarket tx with', createMarketIxs.length, 'ixs');
54+
}
55+
56+
// Optionally, change default market params here . TODO to explore later
57+
// kaminoManager.updateLendingMarketIxs(market.address, market);
58+
59+
// 2) Add an asset reserve (example uses USDC)
60+
// initReserve needs an `adminLiquiditySource` token account (ATA) for the mint.
61+
const adminUsdcAta = await getAssociatedTokenAddress(USDC_MINT, admin.address, TOKEN_PROGRAM_ADDRESS);
62+
63+
const priceFeed = {
64+
pythPrice: address(pythPriceStr),
65+
};
66+
67+
const defaults = getDefaultConfigParams();
68+
69+
const assetConfig = new AssetReserveConfig({
70+
mint: USDC_MINT,
71+
mintTokenProgram: TOKEN_PROGRAM_ADDRESS,
72+
tokenName: 'USDC',
73+
mintDecimals: 6,
74+
priceFeed,
75+
loanToValuePct: defaults.loanToValuePct,
76+
liquidationThresholdPct: defaults.liquidationThresholdPct,
77+
borrowRateCurve: defaults.borrowRateCurve,
78+
depositLimit: new Decimal(1_000_000),
79+
borrowLimit: new Decimal(1_000_000),
80+
});
81+
82+
const { reserve, txnIxs } = await kaminoManager.addAssetToMarketIxs({
83+
admin,
84+
adminLiquiditySource: adminUsdcAta,
85+
marketAddress: market.address,
86+
assetConfig,
87+
});
88+
89+
console.log('New reserve:', reserve.address);
90+
91+
if (execute) {
92+
// 2a) Create + init reserve (needs reserve signer)
93+
await sendAndConfirmTx(c, admin, txnIxs[0], [reserve], [], 'OptionCreateMarketReserve-CreateReserve');
94+
95+
// 2b) Update reserve config (oracle, limits, etc)
96+
await sendAndConfirmTx(c, admin, txnIxs[1], [], [], 'OptionCreateMarketReserve-UpdateReserveConfig');
97+
} else {
98+
console.log('[dry-run] Would send reserve create tx with', txnIxs[0].length, 'ixs');
99+
console.log('[dry-run] Would send reserve update tx with', txnIxs[1].length, 'ixs');
100+
}
101+
102+
// 3) Create a vault (base token = USDC) and allocate to the newly created reserve
103+
const vaultConfig = new KaminoVaultConfig({
104+
admin,
105+
tokenMint: USDC_MINT,
106+
tokenMintProgramId: TOKEN_PROGRAM_ADDRESS,
107+
performanceFeeRatePercentage: new Decimal(1.0),
108+
managementFeeRatePercentage: new Decimal(2.0),
109+
name: 'example option create market + reserve',
110+
vaultTokenSymbol: 'USDC',
111+
vaultTokenName: 'OptionCreateMarketReserve',
112+
});
113+
114+
const { vault: vaultKp, initVaultIxs } = await kaminoManager.createVaultIxs(vaultConfig);
115+
const vault = new KaminoVault(c.rpc, vaultKp.address);
116+
console.log('New vault:', vault.address);
117+
118+
if (execute) {
119+
await sendAndConfirmTx(
120+
c,
121+
admin,
122+
[...initVaultIxs.initVaultIxs, initVaultIxs.createLUTIx, initVaultIxs.initSharesMetadataIx],
123+
[vaultKp],
124+
[],
125+
'OptionB2-InitVault'
126+
);
127+
128+
await sleep(2000);
129+
await sendAndConfirmTx(c, admin, initVaultIxs.populateLUTIxs, [], [], 'OptionCreateMarketReserve-PopulateLUT');
130+
131+
const reserveState = await Reserve.fetch(c.rpc, reserve.address);
132+
if (!reserveState) {
133+
throw new Error(`Reserve not found after creation: ${reserve.address}`);
134+
}
135+
136+
const alloc = new ReserveAllocationConfig({ address: reserve.address, state: reserveState }, 100, new Decimal(1000));
137+
const setAllocIxs = await kaminoManager.updateVaultReserveAllocationIxs(vault, alloc);
138+
139+
await sendAndConfirmTx(
140+
c,
141+
admin,
142+
[setAllocIxs.updateReserveAllocationIx, ...setAllocIxs.updateLUTIxs],
143+
[],
144+
[],
145+
'OptionB2-SetReserveAllocation'
146+
);
147+
148+
console.log('Allocated vault to new reserve:', reserve.address);
149+
} else {
150+
console.log('[dry-run] Vault + allocation steps are ready (set EXECUTE=true to run).');
151+
}
152+
})().catch(async (e) => {
153+
console.error(e);
154+
});
Lines changed: 89 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,89 @@
1+
import { getConnectionPool } from '../utils/connection';
2+
import { getKeypair } from '../utils/keypair';
3+
import { USDC_MINT, USDC_RESERVE_JLP_MARKET } from '../utils/constants';
4+
import Decimal from 'decimal.js/decimal';
5+
import {
6+
getMedianSlotDurationInMsFromLastEpochs,
7+
KaminoManager,
8+
KaminoVault,
9+
KaminoVaultConfig,
10+
Reserve,
11+
ReserveAllocationConfig,
12+
type ReserveWithAddress,
13+
sleep,
14+
} from '@kamino-finance/klend-sdk';
15+
import { TOKEN_PROGRAM_ADDRESS } from '@solana-program/token';
16+
import { sendAndConfirmTx } from '../utils/tx';
17+
18+
/**
19+
* Option: Create your own vault that invests into existing reserves/markets
20+
* - You are the vault admin.
21+
* - You pick which existing reserves to allocate to.
22+
*/
23+
(async () => {
24+
const c = getConnectionPool();
25+
const admin = await getKeypair();
26+
27+
const slotDuration = await getMedianSlotDurationInMsFromLastEpochs();
28+
const kaminoManager = new KaminoManager(c.rpc, slotDuration);
29+
30+
// 1) Create the vault (USDC base token)
31+
const vaultConfig = new KaminoVaultConfig({
32+
admin,
33+
tokenMint: USDC_MINT,
34+
tokenMintProgramId: TOKEN_PROGRAM_ADDRESS,
35+
performanceFeeRatePercentage: new Decimal(1.0),
36+
managementFeeRatePercentage: new Decimal(2.0),
37+
name: 'example option create vault allocate existing reserve',
38+
vaultTokenSymbol: 'USDC',
39+
vaultTokenName: 'OptionCreateVaultAllocateExistingReserve',
40+
});
41+
42+
const { vault: vaultKp, initVaultIxs } = await kaminoManager.createVaultIxs(vaultConfig);
43+
const vault = new KaminoVault(c.rpc, vaultKp.address);
44+
45+
console.log('New vault:', vault.address);
46+
47+
// Init vault + LUT + shares metadata (minimal init)
48+
await sendAndConfirmTx(
49+
c,
50+
admin,
51+
[...initVaultIxs.initVaultIxs, initVaultIxs.createLUTIx, initVaultIxs.initSharesMetadataIx],
52+
[vaultKp],
53+
[],
54+
'OptionCreateVaultAllocateExistingReserve-InitVault'
55+
);
56+
57+
// LUT population is a separate tx
58+
await sleep(2000);
59+
await sendAndConfirmTx(c, admin, initVaultIxs.populateLUTIxs, [], [], 'OptionCreateVaultAllocateExistingReserve-PopulateLUT');
60+
61+
// 2) Allocate to an EXISTING USDC reserve (example: USDC reserve in JLP market)
62+
const reserveState = await Reserve.fetch(c.rpc, USDC_RESERVE_JLP_MARKET);
63+
if (!reserveState) {
64+
throw new Error(`Reserve not found: ${USDC_RESERVE_JLP_MARKET}`);
65+
}
66+
67+
const reserveWithAddress: ReserveWithAddress = {
68+
address: USDC_RESERVE_JLP_MARKET,
69+
state: reserveState,
70+
};
71+
72+
// weight is relative; cap is in token units (USDC)
73+
const allocation = new ReserveAllocationConfig(reserveWithAddress, 100, new Decimal(1000));
74+
75+
const setAllocIxs = await kaminoManager.updateVaultReserveAllocationIxs(vault, allocation);
76+
77+
await sendAndConfirmTx(
78+
c,
79+
admin,
80+
[setAllocIxs.updateReserveAllocationIx, ...setAllocIxs.updateLUTIxs],
81+
[],
82+
[],
83+
'OptionCreateVaultAllocateExistingReserve-SetReserveAllocation'
84+
);
85+
86+
console.log('Allocated vault to existing reserve:', USDC_RESERVE_JLP_MARKET);
87+
})().catch(async (e) => {
88+
console.error(e);
89+
});
Lines changed: 57 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,57 @@
1+
import { getConnectionPool } from '../utils/connection';
2+
import { getKeypair } from '../utils/keypair';
3+
import { EXAMPLE_USDC_VAULT } from '../utils/constants';
4+
import Decimal from 'decimal.js/decimal';
5+
import { getMedianSlotDurationInMsFromLastEpochs, KaminoManager, KaminoVault } from '@kamino-finance/klend-sdk';
6+
import { sendAndConfirmTx } from '../utils/tx';
7+
8+
/**
9+
* Option: Use an existing curated vault
10+
* - You are NOT the admin.
11+
* - You just read info + deposit/withdraw.
12+
*/
13+
(async () => {
14+
const c = getConnectionPool();
15+
const user = await getKeypair();
16+
17+
const slotDuration = await getMedianSlotDurationInMsFromLastEpochs();
18+
const kaminoManager = new KaminoManager(c.rpc, slotDuration);
19+
20+
const vault = new KaminoVault(c.rpc, EXAMPLE_USDC_VAULT);
21+
const vaultState = await vault.getState();
22+
23+
console.log('Vault:', vault.address);
24+
console.log('Base token mint:', vaultState.tokenMint);
25+
console.log('Shares mint:', vaultState.sharesMint);
26+
27+
// Read user shares balances (unstaked + staked in vault farm, if any)
28+
const userSharesBefore = await kaminoManager.getUserSharesBalanceSingleVault(user.address, vault);
29+
console.log('User shares before:', userSharesBefore);
30+
31+
// Read vault holdings (available/uninvested + invested breakdown)
32+
const holdingsBefore = await vault.getVaultHoldings();
33+
holdingsBefore.print();
34+
35+
// Deposit a small amount (in token units, NOT lamports, will be converted internally)
36+
const amountToDeposit = new Decimal(1);
37+
38+
const depositIx = await kaminoManager.depositToVaultIxs(user, vault, amountToDeposit);
39+
40+
await sendAndConfirmTx(
41+
c,
42+
user,
43+
[...depositIx.depositIxs, ...depositIx.stakeInFarmIfNeededIxs],
44+
[],
45+
[vaultState.vaultLookupTable],
46+
'Option-DepositToCuratedVault'
47+
);
48+
49+
// Refresh shares + holdings
50+
const userSharesAfter = await kaminoManager.getUserSharesBalanceSingleVault(user.address, vault);
51+
console.log('User shares after:', userSharesAfter);
52+
53+
const holdingsAfter = await vault.getVaultHoldings();
54+
holdingsAfter.print();
55+
})().catch(async (e) => {
56+
console.error(e);
57+
});

examples/package.json

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -55,6 +55,9 @@
5555
"kvault:withdraw_everything_and_block_single_reserve": "yarn tsx kvault-examples/example_manager_withdraw_everything_and_block_invest_in_single_reserve.ts",
5656
"kvault:withdraw_pending_fees": "yarn tsx kvault-examples/example_manager_withdraw_pending_fees.ts",
5757
"kvault:set_unallocated_weight_and_cap": "yarn tsx kvault-examples/example_set_unallocated_weight_and_cap.ts",
58+
"kvault:option_use_curated_vault": "yarn tsx kvault-examples/example_option_use_curated_vault.ts",
59+
"kvault:option_create_vault_and_allocate": "yarn tsx kvault-examples/example_option_create_vault_and_allocate_to_existing_reserve.ts",
60+
"kvault:option_create_market_reserve_and_vault": "yarn tsx kvault-examples/example_option_create_market_add_reserve_then_create_vault.ts",
5861
"kvault:update_vault_config": "yarn tsx kvault-examples/example_update_vault_config.ts",
5962
"kvault:update_vault_fees": "yarn tsx kvault-examples/example_update_vault_fees.ts",
6063
"kvault:user_claim_rewards": "yarn tsx kvault-examples/example_user_claim_rewards.ts",
@@ -86,4 +89,4 @@
8689
"tsx": "^4.19.1",
8790
"typescript": "^5.4.5"
8891
}
89-
}
92+
}

package.json

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -59,6 +59,7 @@
5959
"@coral-xyz/anchor": "^0.28.0",
6060
"@coral-xyz/borsh": "^0.28.0",
6161
"@kamino-finance/farms-sdk": "3.2.14",
62+
"@kamino-finance/klend-sdk": "^7.3.13",
6263
"@kamino-finance/kliquidity-sdk": "^8.5.3",
6364
"@kamino-finance/scope-sdk": "^10.1.0",
6465
"@solana-program/address-lookup-table": "^0.8.0",

yarn.lock

Lines changed: 29 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -856,6 +856,35 @@
856856
bn.js "^5.2.1"
857857
decimal.js "^10.4.3"
858858

859+
"@kamino-finance/klend-sdk@^7.3.13":
860+
version "7.3.13"
861+
resolved "https://registry.yarnpkg.com/@kamino-finance/klend-sdk/-/klend-sdk-7.3.13.tgz#2cf0d50ee8905f597a5c525f011ecf61912a9eff"
862+
integrity sha512-RkA8rGm/wxXemf2zMzm0pUw4iXpq8OK8pUtdxyN6BlU62crXj1JQafzwVNGngLVrFGInKk/4C++lmn6zKBa/wQ==
863+
dependencies:
864+
"@coral-xyz/anchor" "^0.28.0"
865+
"@coral-xyz/borsh" "^0.28.0"
866+
"@kamino-finance/farms-sdk" "3.2.14"
867+
"@kamino-finance/kliquidity-sdk" "^8.5.3"
868+
"@kamino-finance/scope-sdk" "^10.1.0"
869+
"@solana-program/address-lookup-table" "^0.8.0"
870+
"@solana-program/system" "^0.8.0"
871+
"@solana-program/token" "^0.6.0"
872+
"@solana-program/token-2022" "^0.5.0"
873+
"@solana/buffer-layout" "^4.0.1"
874+
"@solana/compat" "^2.3.0"
875+
"@solana/kit" "^2.3.0"
876+
"@solana/spl-stake-pool" "^1.1.8"
877+
"@solana/sysvars" "^2.3.0"
878+
"@solana/web3.js" "^1.98.2"
879+
axios "^1.6.8"
880+
bn.js "^5.2.1"
881+
buffer "^6.0.3"
882+
commander "^9.3.0"
883+
decimal.js "^10.4.3"
884+
exponential-backoff "^3.1.1"
885+
fzstd "^0.1.1"
886+
zstddec "^0.1.0"
887+
859888
"@kamino-finance/kliquidity-sdk@^8.4.0":
860889
version "8.4.5"
861890
resolved "https://registry.yarnpkg.com/@kamino-finance/kliquidity-sdk/-/kliquidity-sdk-8.4.5.tgz#d569fae5ffde843cde9c9c22aa9467b68b3dd3bc"

0 commit comments

Comments
 (0)