Skip to content
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view

Large diffs are not rendered by default.

Original file line number Diff line number Diff line change
@@ -0,0 +1,154 @@
import { getConnectionPool } from '../utils/connection';
import { getKeypair } from '../utils/keypair';
import { USDC_MINT } from '../utils/constants';
import Decimal from 'decimal.js/decimal';
import {
AssetReserveConfig,
getAssociatedTokenAddress,
getDefaultConfigParams,
getMedianSlotDurationInMsFromLastEpochs,
KaminoManager,
KaminoVault,
KaminoVaultConfig,
LendingMarket,
Reserve,
ReserveAllocationConfig,
sleep,
} from '@kamino-finance/klend-sdk';
import { address } from '@solana/kit';
import { TOKEN_PROGRAM_ADDRESS } from '@solana-program/token';
import { sendAndConfirmTx } from '../utils/tx';

/**
* Option: Create your own lending market + add a reserve, then create a vault on top
* - You are the market admin AND the vault admin.
* - This is advanced: you must provide valid oracle configuration.
*
* Required env vars:
* - `PYTH_PRICE` = Pyth price account for the asset you add (base58 address)
*
* Optional:
* - `EXECUTE=true` to actually send transactions (default is dry-run).
*/
(async () => {
const c = getConnectionPool();
const admin = await getKeypair();

const execute = (process.env.EXECUTE ?? 'false').toLowerCase() === 'true';
const pythPriceStr = process.env.PYTH_PRICE;
if (!pythPriceStr) {
throw new Error('Missing env var PYTH_PRICE (Pyth price account address)');
}

const slotDuration = await getMedianSlotDurationInMsFromLastEpochs();
const kaminoManager = new KaminoManager(c.rpc, slotDuration);

// 1) Create a new lending market
const { market, ixs: createMarketIxs } = await kaminoManager.createMarketIxs({ admin });
console.log('New market:', market.address);

if (execute) {
await sendAndConfirmTx(c, admin, createMarketIxs, [market], [], 'OptionCreateMarketReserve-CreateMarket');
} else {
console.log('[dry-run] Would send createMarket tx with', createMarketIxs.length, 'ixs');
}

// Optionally, change default market params here . TODO to explore later
// kaminoManager.updateLendingMarketIxs(market.address, market);

// 2) Add an asset reserve (example uses USDC)
// initReserve needs an `adminLiquiditySource` token account (ATA) for the mint.
const adminUsdcAta = await getAssociatedTokenAddress(USDC_MINT, admin.address, TOKEN_PROGRAM_ADDRESS);

const priceFeed = {
pythPrice: address(pythPriceStr),
};

const defaults = getDefaultConfigParams();

const assetConfig = new AssetReserveConfig({
mint: USDC_MINT,
mintTokenProgram: TOKEN_PROGRAM_ADDRESS,
tokenName: 'USDC',
mintDecimals: 6,
priceFeed,
loanToValuePct: defaults.loanToValuePct,
liquidationThresholdPct: defaults.liquidationThresholdPct,
borrowRateCurve: defaults.borrowRateCurve,
depositLimit: new Decimal(1_000_000),
borrowLimit: new Decimal(1_000_000),
});

const { reserve, txnIxs } = await kaminoManager.addAssetToMarketIxs({
admin,
adminLiquiditySource: adminUsdcAta,
marketAddress: market.address,
assetConfig,
});

console.log('New reserve:', reserve.address);

if (execute) {
// 2a) Create + init reserve (needs reserve signer)
await sendAndConfirmTx(c, admin, txnIxs[0], [reserve], [], 'OptionCreateMarketReserve-CreateReserve');

// 2b) Update reserve config (oracle, limits, etc)
await sendAndConfirmTx(c, admin, txnIxs[1], [], [], 'OptionCreateMarketReserve-UpdateReserveConfig');
} else {
console.log('[dry-run] Would send reserve create tx with', txnIxs[0].length, 'ixs');
console.log('[dry-run] Would send reserve update tx with', txnIxs[1].length, 'ixs');
}

// 3) Create a vault (base token = USDC) and allocate to the newly created reserve
const vaultConfig = new KaminoVaultConfig({
admin,
tokenMint: USDC_MINT,
tokenMintProgramId: TOKEN_PROGRAM_ADDRESS,
performanceFeeRatePercentage: new Decimal(1.0),
managementFeeRatePercentage: new Decimal(2.0),
name: 'example option create market + reserve',
vaultTokenSymbol: 'USDC',
vaultTokenName: 'OptionCreateMarketReserve',
});

const { vault: vaultKp, initVaultIxs } = await kaminoManager.createVaultIxs(vaultConfig);
const vault = new KaminoVault(c.rpc, vaultKp.address);
console.log('New vault:', vault.address);

if (execute) {
await sendAndConfirmTx(
c,
admin,
[...initVaultIxs.initVaultIxs, initVaultIxs.createLUTIx, initVaultIxs.initSharesMetadataIx],
[vaultKp],
[],
'OptionB2-InitVault'
);

await sleep(2000);
await sendAndConfirmTx(c, admin, initVaultIxs.populateLUTIxs, [], [], 'OptionCreateMarketReserve-PopulateLUT');

const reserveState = await Reserve.fetch(c.rpc, reserve.address);
if (!reserveState) {
throw new Error(`Reserve not found after creation: ${reserve.address}`);
}

const alloc = new ReserveAllocationConfig({ address: reserve.address, state: reserveState }, 100, new Decimal(1000));
const setAllocIxs = await kaminoManager.updateVaultReserveAllocationIxs(vault, alloc);

await sendAndConfirmTx(
c,
admin,
[setAllocIxs.updateReserveAllocationIx, ...setAllocIxs.updateLUTIxs],
[],
[],
'OptionB2-SetReserveAllocation'
);

console.log('Allocated vault to new reserve:', reserve.address);
} else {
console.log('[dry-run] Vault + allocation steps are ready (set EXECUTE=true to run).');
}
})().catch(async (e) => {
console.error(e);
});
Original file line number Diff line number Diff line change
@@ -0,0 +1,89 @@
import { getConnectionPool } from '../utils/connection';
import { getKeypair } from '../utils/keypair';
import { USDC_MINT, USDC_RESERVE_JLP_MARKET } from '../utils/constants';
import Decimal from 'decimal.js/decimal';
import {
getMedianSlotDurationInMsFromLastEpochs,
KaminoManager,
KaminoVault,
KaminoVaultConfig,
Reserve,
ReserveAllocationConfig,
type ReserveWithAddress,
sleep,
} from '@kamino-finance/klend-sdk';
import { TOKEN_PROGRAM_ADDRESS } from '@solana-program/token';
import { sendAndConfirmTx } from '../utils/tx';

/**
* Option: Create your own vault that invests into existing reserves/markets
* - You are the vault admin.
* - You pick which existing reserves to allocate to.
*/
(async () => {
const c = getConnectionPool();
const admin = await getKeypair();

const slotDuration = await getMedianSlotDurationInMsFromLastEpochs();
const kaminoManager = new KaminoManager(c.rpc, slotDuration);

// 1) Create the vault (USDC base token)
const vaultConfig = new KaminoVaultConfig({
admin,
tokenMint: USDC_MINT,
tokenMintProgramId: TOKEN_PROGRAM_ADDRESS,
performanceFeeRatePercentage: new Decimal(1.0),
managementFeeRatePercentage: new Decimal(2.0),
name: 'example option create vault allocate existing reserve',
vaultTokenSymbol: 'USDC',
vaultTokenName: 'OptionCreateVaultAllocateExistingReserve',
});

const { vault: vaultKp, initVaultIxs } = await kaminoManager.createVaultIxs(vaultConfig);
const vault = new KaminoVault(c.rpc, vaultKp.address);

console.log('New vault:', vault.address);

// Init vault + LUT + shares metadata (minimal init)
await sendAndConfirmTx(
c,
admin,
[...initVaultIxs.initVaultIxs, initVaultIxs.createLUTIx, initVaultIxs.initSharesMetadataIx],
[vaultKp],
[],
'OptionCreateVaultAllocateExistingReserve-InitVault'
);

// LUT population is a separate tx
await sleep(2000);
await sendAndConfirmTx(c, admin, initVaultIxs.populateLUTIxs, [], [], 'OptionCreateVaultAllocateExistingReserve-PopulateLUT');

// 2) Allocate to an EXISTING USDC reserve (example: USDC reserve in JLP market)
const reserveState = await Reserve.fetch(c.rpc, USDC_RESERVE_JLP_MARKET);
if (!reserveState) {
throw new Error(`Reserve not found: ${USDC_RESERVE_JLP_MARKET}`);
}

const reserveWithAddress: ReserveWithAddress = {
address: USDC_RESERVE_JLP_MARKET,
state: reserveState,
};

// weight is relative; cap is in token units (USDC)
const allocation = new ReserveAllocationConfig(reserveWithAddress, 100, new Decimal(1000));

const setAllocIxs = await kaminoManager.updateVaultReserveAllocationIxs(vault, allocation);

await sendAndConfirmTx(
c,
admin,
[setAllocIxs.updateReserveAllocationIx, ...setAllocIxs.updateLUTIxs],
[],
[],
'OptionCreateVaultAllocateExistingReserve-SetReserveAllocation'
);

console.log('Allocated vault to existing reserve:', USDC_RESERVE_JLP_MARKET);
})().catch(async (e) => {
console.error(e);
});
57 changes: 57 additions & 0 deletions examples/kvault-examples/example_option_use_curated_vault.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,57 @@
import { getConnectionPool } from '../utils/connection';
import { getKeypair } from '../utils/keypair';
import { EXAMPLE_USDC_VAULT } from '../utils/constants';
import Decimal from 'decimal.js/decimal';
import { getMedianSlotDurationInMsFromLastEpochs, KaminoManager, KaminoVault } from '@kamino-finance/klend-sdk';
import { sendAndConfirmTx } from '../utils/tx';

/**
* Option: Use an existing curated vault
* - You are NOT the admin.
* - You just read info + deposit/withdraw.
*/
(async () => {
const c = getConnectionPool();
const user = await getKeypair();

const slotDuration = await getMedianSlotDurationInMsFromLastEpochs();
const kaminoManager = new KaminoManager(c.rpc, slotDuration);

const vault = new KaminoVault(c.rpc, EXAMPLE_USDC_VAULT);
const vaultState = await vault.getState();

console.log('Vault:', vault.address);
console.log('Base token mint:', vaultState.tokenMint);
console.log('Shares mint:', vaultState.sharesMint);

// Read user shares balances (unstaked + staked in vault farm, if any)
const userSharesBefore = await kaminoManager.getUserSharesBalanceSingleVault(user.address, vault);
console.log('User shares before:', userSharesBefore);

// Read vault holdings (available/uninvested + invested breakdown)
const holdingsBefore = await vault.getVaultHoldings();
holdingsBefore.print();

// Deposit a small amount (in token units, NOT lamports, will be converted internally)
const amountToDeposit = new Decimal(1);

const depositIx = await kaminoManager.depositToVaultIxs(user, vault, amountToDeposit);

await sendAndConfirmTx(
c,
user,
[...depositIx.depositIxs, ...depositIx.stakeInFarmIfNeededIxs],
[],
[vaultState.vaultLookupTable],
'Option-DepositToCuratedVault'
);

// Refresh shares + holdings
const userSharesAfter = await kaminoManager.getUserSharesBalanceSingleVault(user.address, vault);
console.log('User shares after:', userSharesAfter);

const holdingsAfter = await vault.getVaultHoldings();
holdingsAfter.print();
})().catch(async (e) => {
console.error(e);
});
5 changes: 4 additions & 1 deletion examples/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -55,6 +55,9 @@
"kvault:withdraw_everything_and_block_single_reserve": "yarn tsx kvault-examples/example_manager_withdraw_everything_and_block_invest_in_single_reserve.ts",
"kvault:withdraw_pending_fees": "yarn tsx kvault-examples/example_manager_withdraw_pending_fees.ts",
"kvault:set_unallocated_weight_and_cap": "yarn tsx kvault-examples/example_set_unallocated_weight_and_cap.ts",
"kvault:option_use_curated_vault": "yarn tsx kvault-examples/example_option_use_curated_vault.ts",
"kvault:option_create_vault_and_allocate": "yarn tsx kvault-examples/example_option_create_vault_and_allocate_to_existing_reserve.ts",
"kvault:option_create_market_reserve_and_vault": "yarn tsx kvault-examples/example_option_create_market_add_reserve_then_create_vault.ts",
"kvault:update_vault_config": "yarn tsx kvault-examples/example_update_vault_config.ts",
"kvault:update_vault_fees": "yarn tsx kvault-examples/example_update_vault_fees.ts",
"kvault:user_claim_rewards": "yarn tsx kvault-examples/example_user_claim_rewards.ts",
Expand Down Expand Up @@ -86,4 +89,4 @@
"tsx": "^4.19.1",
"typescript": "^5.4.5"
}
}
}
1 change: 1 addition & 0 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -59,6 +59,7 @@
"@coral-xyz/anchor": "^0.28.0",
"@coral-xyz/borsh": "^0.28.0",
"@kamino-finance/farms-sdk": "3.2.14",
"@kamino-finance/klend-sdk": "^7.3.13",
"@kamino-finance/kliquidity-sdk": "^8.5.3",
"@kamino-finance/scope-sdk": "^10.1.0",
"@solana-program/address-lookup-table": "^0.8.0",
Expand Down
29 changes: 29 additions & 0 deletions yarn.lock
Original file line number Diff line number Diff line change
Expand Up @@ -856,6 +856,35 @@
bn.js "^5.2.1"
decimal.js "^10.4.3"

"@kamino-finance/klend-sdk@^7.3.13":
version "7.3.13"
resolved "https://registry.yarnpkg.com/@kamino-finance/klend-sdk/-/klend-sdk-7.3.13.tgz#2cf0d50ee8905f597a5c525f011ecf61912a9eff"
integrity sha512-RkA8rGm/wxXemf2zMzm0pUw4iXpq8OK8pUtdxyN6BlU62crXj1JQafzwVNGngLVrFGInKk/4C++lmn6zKBa/wQ==
dependencies:
"@coral-xyz/anchor" "^0.28.0"
"@coral-xyz/borsh" "^0.28.0"
"@kamino-finance/farms-sdk" "3.2.14"
"@kamino-finance/kliquidity-sdk" "^8.5.3"
"@kamino-finance/scope-sdk" "^10.1.0"
"@solana-program/address-lookup-table" "^0.8.0"
"@solana-program/system" "^0.8.0"
"@solana-program/token" "^0.6.0"
"@solana-program/token-2022" "^0.5.0"
"@solana/buffer-layout" "^4.0.1"
"@solana/compat" "^2.3.0"
"@solana/kit" "^2.3.0"
"@solana/spl-stake-pool" "^1.1.8"
"@solana/sysvars" "^2.3.0"
"@solana/web3.js" "^1.98.2"
axios "^1.6.8"
bn.js "^5.2.1"
buffer "^6.0.3"
commander "^9.3.0"
decimal.js "^10.4.3"
exponential-backoff "^3.1.1"
fzstd "^0.1.1"
zstddec "^0.1.0"

"@kamino-finance/kliquidity-sdk@^8.4.0":
version "8.4.5"
resolved "https://registry.yarnpkg.com/@kamino-finance/kliquidity-sdk/-/kliquidity-sdk-8.4.5.tgz#d569fae5ffde843cde9c9c22aa9467b68b3dd3bc"
Expand Down