Skip to main content

kvault_interface/instructions/
withdraw.rs

1use borsh::BorshSerialize;
2use solana_instruction::{AccountMeta, Instruction};
3use solana_pubkey::Pubkey;
4
5use crate::{discriminators, pda, util::*, KVAULT_PROGRAM_ID};
6
7// ---------------------------------------------------------------------------
8// withdraw / sell — full withdraw (from available + from invested)
9// ---------------------------------------------------------------------------
10
11/// Account addresses required by the `withdraw` and `sell` instructions.
12///
13/// Combines accounts for both the "withdraw from available" and
14/// "withdraw from invested" phases of a full withdrawal.
15pub struct WithdrawAccounts {
16    // WithdrawFromAvailable
17    pub user: Pubkey,
18    pub vault_state: Pubkey,
19    pub global_config: Pubkey,
20    pub token_vault: Pubkey,
21    pub base_vault_authority: Pubkey,
22    pub user_token_ata: Pubkey,
23    pub token_mint: Pubkey,
24    pub user_shares_ata: Pubkey,
25    pub shares_mint: Pubkey,
26    pub token_program: Pubkey,
27    pub shares_token_program: Pubkey,
28    pub klend_program: Pubkey,
29    // WithdrawFromInvested
30    pub invested_vault_state: Pubkey,
31    pub reserve: Pubkey,
32    pub ctoken_vault: Pubkey,
33    pub lending_market: Pubkey,
34    pub lending_market_authority: Pubkey,
35    pub reserve_liquidity_supply: Pubkey,
36    pub reserve_collateral_mint: Pubkey,
37    pub reserve_collateral_token_program: Pubkey,
38    pub instruction_sysvar_account: Pubkey,
39}
40
41fn build_withdraw_accounts(accounts: &WithdrawAccounts) -> Vec<AccountMeta> {
42    let (evt_auth, _) = pda::event_authority(&KVAULT_PROGRAM_ID);
43    vec![
44        // WithdrawFromAvailable
45        signer(accounts.user),
46        writable(accounts.vault_state),
47        readonly(accounts.global_config),
48        writable(accounts.token_vault),
49        readonly(accounts.base_vault_authority),
50        writable(accounts.user_token_ata),
51        writable(accounts.token_mint),
52        writable(accounts.user_shares_ata),
53        writable(accounts.shares_mint),
54        readonly(accounts.token_program),
55        readonly(accounts.shares_token_program),
56        readonly(accounts.klend_program),
57        // event_cpi (on WithdrawFromAvailable)
58        readonly(evt_auth),
59        readonly(KVAULT_PROGRAM_ID),
60        // WithdrawFromInvested
61        writable(accounts.invested_vault_state),
62        writable(accounts.reserve),
63        writable(accounts.ctoken_vault),
64        readonly(accounts.lending_market),
65        readonly(accounts.lending_market_authority),
66        writable(accounts.reserve_liquidity_supply),
67        writable(accounts.reserve_collateral_mint),
68        readonly(accounts.reserve_collateral_token_program),
69        readonly(accounts.instruction_sysvar_account),
70        // event_cpi (on Withdraw)
71        readonly(evt_auth),
72        readonly(KVAULT_PROGRAM_ID),
73    ]
74}
75
76/// Build a raw `withdraw` instruction.
77///
78/// Burns `shares_amount` vault shares and returns the underlying tokens.
79/// If available liquidity is insufficient, the program automatically
80/// disinvests from the specified reserve. Pass `u64::MAX` to burn the caller's
81/// entire share balance — the program clamps `shares_amount` to the balance
82/// actually held. `remaining_accounts` must contain the refresh list: writable
83/// reserve metas followed by readonly lending-market metas, in allocation-slot
84/// order.
85///
86/// # Farms
87///
88/// If the vault has a share farm (`VaultState::vault_farm` is set), the
89/// caller's shares are staked in the farm and must be unstaked from it (a
90/// separate Kamino Farms instruction, prepended to the transaction) before
91/// they can be burned here.
92pub fn withdraw(
93    accounts: WithdrawAccounts,
94    shares_amount: u64,
95    remaining_accounts: Vec<AccountMeta>,
96) -> Instruction {
97    #[derive(BorshSerialize)]
98    struct Args {
99        shares_amount: u64,
100    }
101
102    let args = Args { shares_amount };
103    let mut data = discriminators::WITHDRAW.to_vec();
104    args.serialize(&mut data).unwrap();
105
106    let mut account_metas = build_withdraw_accounts(&accounts);
107    account_metas.extend(remaining_accounts);
108
109    Instruction {
110        program_id: KVAULT_PROGRAM_ID,
111        accounts: account_metas,
112        data,
113    }
114}
115
116/// Build a raw `sell` instruction.
117///
118/// Same accounts and semantics as [`withdraw`], but uses the `sell`
119/// discriminator. The amount is interpreted as a share amount; `u64::MAX`
120/// burns the caller's entire balance, and the farm-unstake requirement
121/// described on [`withdraw`] applies here too.
122pub fn sell(
123    accounts: WithdrawAccounts,
124    shares_amount: u64,
125    remaining_accounts: Vec<AccountMeta>,
126) -> Instruction {
127    #[derive(BorshSerialize)]
128    struct Args {
129        shares_amount: u64,
130    }
131
132    let args = Args { shares_amount };
133    let mut data = discriminators::SELL.to_vec();
134    args.serialize(&mut data).unwrap();
135
136    let mut account_metas = build_withdraw_accounts(&accounts);
137    account_metas.extend(remaining_accounts);
138
139    Instruction {
140        program_id: KVAULT_PROGRAM_ID,
141        accounts: account_metas,
142        data,
143    }
144}
145
146// ---------------------------------------------------------------------------
147// withdraw_from_available
148// ---------------------------------------------------------------------------
149
150/// Account addresses required by the `withdraw_from_available` instruction.
151///
152/// Only interacts with the vault's available (uninvested) liquidity — no
153/// reserve disinvestment occurs.
154pub struct WithdrawFromAvailableAccounts {
155    pub user: Pubkey,
156    pub vault_state: Pubkey,
157    pub global_config: Pubkey,
158    pub token_vault: Pubkey,
159    pub base_vault_authority: Pubkey,
160    pub user_token_ata: Pubkey,
161    pub token_mint: Pubkey,
162    pub user_shares_ata: Pubkey,
163    pub shares_mint: Pubkey,
164    pub token_program: Pubkey,
165    pub shares_token_program: Pubkey,
166    pub klend_program: Pubkey,
167}
168
169/// Build a raw `withdraw_from_available` instruction.
170///
171/// Burns `shares_amount` vault shares and returns tokens exclusively from
172/// the vault's available balance. Fails if available liquidity is insufficient.
173/// `remaining_accounts` must contain the refresh list: writable reserve metas
174/// followed by readonly lending-market metas, in allocation-slot order.
175pub fn withdraw_from_available(
176    accounts: WithdrawFromAvailableAccounts,
177    shares_amount: u64,
178    remaining_accounts: Vec<AccountMeta>,
179) -> Instruction {
180    #[derive(BorshSerialize)]
181    struct Args {
182        shares_amount: u64,
183    }
184
185    let args = Args { shares_amount };
186    let mut data = discriminators::WITHDRAW_FROM_AVAILABLE.to_vec();
187    args.serialize(&mut data).unwrap();
188
189    let (evt_auth, _) = pda::event_authority(&KVAULT_PROGRAM_ID);
190    let mut account_metas = vec![
191        signer(accounts.user),
192        writable(accounts.vault_state),
193        readonly(accounts.global_config),
194        writable(accounts.token_vault),
195        readonly(accounts.base_vault_authority),
196        writable(accounts.user_token_ata),
197        writable(accounts.token_mint),
198        writable(accounts.user_shares_ata),
199        writable(accounts.shares_mint),
200        readonly(accounts.token_program),
201        readonly(accounts.shares_token_program),
202        readonly(accounts.klend_program),
203        // event_cpi accounts
204        readonly(evt_auth),
205        readonly(KVAULT_PROGRAM_ID),
206    ];
207    account_metas.extend(remaining_accounts);
208
209    Instruction {
210        program_id: KVAULT_PROGRAM_ID,
211        accounts: account_metas,
212        data,
213    }
214}