1use solana_program::pubkey::Pubkey;
2use spl_associated_token_account::get_associated_token_address;
3use steel::*;
4
5use crate::{consts::*, instruction::*, state::*};
6
7pub fn kamino_accounts(vault_address: Pubkey) -> Vec<AccountMeta> {
13 let vault_ctoken_address = get_associated_token_address(&vault_address, &CTOKEN_ADDRESS);
14
15 vec![
16 AccountMeta::new(vault_address, false),
17 AccountMeta::new(OBLIGATION_ADDRESS, false),
18 AccountMeta::new(LENDING_MARKET_ADDRESS, false),
19 AccountMeta::new(LENDING_MARKET_AUTHORITY_ADDRESS, false),
20 AccountMeta::new(RESERVE_ADDRESS, false),
21 AccountMeta::new(USDC_ADDRESS, false),
22 AccountMeta::new(RESERVE_LIQUIDITY_SUPPLY_ADDRESS, false),
23 AccountMeta::new(CTOKEN_ADDRESS, false),
24 AccountMeta::new(RESERVE_CTOKEN_ADDRESS, false),
25 AccountMeta::new(get_associated_token_address(&vault_address, &USDC_ADDRESS), false),
26 AccountMeta::new(vault_ctoken_address, false),
27 AccountMeta::new_readonly(spl_token::ID, false),
28 AccountMeta::new_readonly(spl_token::ID, false),
29 AccountMeta::new_readonly(sysvar::instructions::ID, false),
30 AccountMeta::new(RESERVE_FARM_USER_STATE_ADDRESS, false),
31 AccountMeta::new(RESERVE_FARM_STATE_ADDRESS, false),
32 AccountMeta::new_readonly(KFARMS_PROGRAM_ID, false),
33 AccountMeta::new_readonly(SCOPE_PRICES_ADDRESS, false),
34 AccountMeta::new_readonly(KLEND_PROGRAM_ID, false),
35 ]
36}
37
38pub fn perena_accounts(vault_address: Pubkey) -> Vec<AccountMeta> {
41 let vault_usdc_address = get_associated_token_address(&vault_address, &USDC_ADDRESS);
42 let vault_usd_star_address = get_associated_token_address(&vault_address, &USD_STAR_MINT);
43
44 vec![
45 AccountMeta::new(vault_address, false),
47 AccountMeta::new(PERENA_BANK_STATE, false),
49 AccountMeta::new(PERENA_VAULT_STATE, false),
51 AccountMeta::new_readonly(PERENA_ORACLE_STATE, false),
53 AccountMeta::new_readonly(USDC_ADDRESS, false),
55 AccountMeta::new(USD_STAR_MINT, false),
57 AccountMeta::new(vault_usdc_address, false),
59 AccountMeta::new(vault_usd_star_address, false),
61 AccountMeta::new(PERENA_YIELDING_VAULT, false),
63 AccountMeta::new(PERENA_TEAM_STATE, false),
65 AccountMeta::new(PERENA_FEE_TEAM_ATA, false),
67 AccountMeta::new_readonly(system_program::ID, false),
69 AccountMeta::new_readonly(spl_token::ID, false),
71 AccountMeta::new_readonly(spl_token::ID, false),
73 AccountMeta::new_readonly(spl_associated_token_account::ID, false),
75 AccountMeta::new_readonly(PERENA_PROGRAM_ID, false),
77 ]
78}
79
80pub fn protocol_accounts(protocol_id: u8, vault_address: Pubkey) -> Vec<AccountMeta> {
82 match protocol_id {
83 PROTOCOL_KAMINO => kamino_accounts(vault_address),
84 PROTOCOL_PERENA => perena_accounts(vault_address),
85 _ => vec![],
86 }
87}
88
89pub fn init(signer: Pubkey, platform_fee_bps: u16, treasury: Pubkey) -> Instruction {
95 let vault_address = vault_pda().0;
96 let vault_usdc_address = get_associated_token_address(&vault_address, &USDC_ADDRESS);
97
98 Instruction {
99 program_id: crate::ID,
100 accounts: vec![
101 AccountMeta::new(signer, true),
102 AccountMeta::new_readonly(USDC_ADDRESS, false),
103 AccountMeta::new(vault_address, false),
104 AccountMeta::new(vault_usdc_address, false),
105 AccountMeta::new_readonly(system_program::ID, false),
106 AccountMeta::new_readonly(spl_token::ID, false),
107 AccountMeta::new_readonly(spl_associated_token_account::ID, false),
108 ],
109 data: Init {
110 platform_fee_bps: platform_fee_bps.to_le_bytes(),
111 _padding: [0u8; 6],
112 treasury: treasury.to_bytes(),
113 }
114 .to_bytes(),
115 }
116}
117
118pub fn init_protocol(signer: Pubkey, protocol_id: u8, name: &str, user_lookup_table: Option<Pubkey>) -> Instruction {
120 let vault_address = vault_pda().0;
121 let protocol_address = protocol_pda(protocol_id).0;
122
123 let receipt_token_mint = match protocol_id {
125 PROTOCOL_KAMINO => CTOKEN_ADDRESS,
126 PROTOCOL_PERENA => USD_STAR_MINT,
127 _ => panic!("Unknown protocol"),
128 };
129
130 let vault_receipt_token_address = get_associated_token_address(&vault_address, &receipt_token_mint);
131
132 let name_bytes = name.as_bytes();
134 let mut name_array = [0u8; 32];
135 let len = name_bytes.len().min(32);
136 name_array[..len].copy_from_slice(&name_bytes[..len]);
137
138 let mut accounts = vec![
139 AccountMeta::new(signer, true),
140 AccountMeta::new(vault_address, false),
141 AccountMeta::new(protocol_address, false),
142 AccountMeta::new_readonly(receipt_token_mint, false),
143 AccountMeta::new(vault_receipt_token_address, false),
144 AccountMeta::new_readonly(system_program::ID, false),
145 AccountMeta::new_readonly(spl_token::ID, false),
146 AccountMeta::new_readonly(spl_associated_token_account::ID, false),
147 ];
148
149 if protocol_id == PROTOCOL_KAMINO {
151 let user_metadata_address = Pubkey::find_program_address(
152 &[b"user_meta", vault_address.as_ref()],
153 &klend_sdk::programs::KAMINO_LENDING_ID.to_bytes().into(),
154 )
155 .0;
156
157 accounts.extend(vec![
158 AccountMeta::new(OBLIGATION_ADDRESS, false),
159 AccountMeta::new(LENDING_MARKET_ADDRESS, false),
160 AccountMeta::new_readonly(USDC_ADDRESS, false),
161 AccountMeta::new_readonly(USDC_ADDRESS, false),
162 AccountMeta::new(user_metadata_address, false),
163 AccountMeta::new(user_lookup_table.unwrap_or(signer), false),
164 AccountMeta::new(LENDING_MARKET_AUTHORITY_ADDRESS, false),
165 AccountMeta::new(RESERVE_ADDRESS, false),
166 AccountMeta::new(RESERVE_FARM_USER_STATE_ADDRESS, false),
167 AccountMeta::new(RESERVE_FARM_STATE_ADDRESS, false),
168 AccountMeta::new_readonly(KFARMS_PROGRAM_ID, false),
169 AccountMeta::new_readonly(KLEND_PROGRAM_ID, false),
170 AccountMeta::new_readonly(sysvar::rent::ID, false),
171 AccountMeta::new_readonly(system_program::ID, false),
172 ]);
173 }
174
175 Instruction {
176 program_id: crate::ID,
177 accounts,
178 data: InitProtocol {
179 protocol_id,
180 name: name_array,
181 }
182 .to_bytes(),
183 }
184}
185
186pub fn checkpoint(signer: Pubkey, protocol_id: u8) -> Instruction {
188 let vault_address = vault_pda().0;
189 let protocol_address = protocol_pda(protocol_id).0;
190
191 let mut accounts = vec![
192 AccountMeta::new(signer, true),
193 AccountMeta::new(vault_address, false),
194 AccountMeta::new(protocol_address, false),
195 ];
196
197 match protocol_id {
199 PROTOCOL_KAMINO => {
200 accounts.push(AccountMeta::new_readonly(RESERVE_ADDRESS, false));
201 }
202 PROTOCOL_PERENA => {
203 accounts.push(AccountMeta::new_readonly(PERENA_ORACLE_STATE, false));
204 }
205 _ => {}
206 }
207
208 Instruction {
209 program_id: crate::ID,
210 accounts,
211 data: Checkpoint { protocol_id }.to_bytes(),
212 }
213}
214
215pub fn rebalance(
221 signer: Pubkey,
222 stable_address: Pubkey,
223 from_protocol: u8,
224 to_protocol: u8,
225 amount: u64,
226) -> Instruction {
227 let vault_address = vault_pda().0;
228 let vault_usdc_address = get_associated_token_address(&vault_address, &USDC_ADDRESS);
229 let from_protocol_address = protocol_pda(from_protocol).0;
230 let to_protocol_address = protocol_pda(to_protocol).0;
231 let from_position_address = position_pda(stable_address, from_protocol).0;
232 let to_position_address = position_pda(stable_address, to_protocol).0;
233
234 let mut accounts = vec![
235 AccountMeta::new(signer, true),
236 AccountMeta::new(vault_address, false),
237 AccountMeta::new(stable_address, false),
238 AccountMeta::new(from_protocol_address, false),
239 AccountMeta::new(to_protocol_address, false),
240 AccountMeta::new(from_position_address, false),
241 AccountMeta::new(to_position_address, false),
242 AccountMeta::new(vault_usdc_address, false),
243 AccountMeta::new_readonly(system_program::ID, false),
244 AccountMeta::new_readonly(spl_token::ID, false),
245 ];
246
247 accounts.extend(protocol_accounts(from_protocol, vault_address));
249 accounts.extend(protocol_accounts(to_protocol, vault_address));
251
252 Instruction {
253 program_id: crate::ID,
254 accounts,
255 data: Rebalance {
256 from_protocol,
257 to_protocol,
258 _padding: [0u8; 6],
259 amount: amount.to_le_bytes(),
260 }
261 .to_bytes(),
262 }
263}
264
265pub fn set_primary_protocol(signer: Pubkey, protocol_id: u8) -> Instruction {
267 let vault_address = vault_pda().0;
268 let protocol_address = protocol_pda(protocol_id).0;
269
270 Instruction {
271 program_id: crate::ID,
272 accounts: vec![
273 AccountMeta::new(signer, true),
274 AccountMeta::new(vault_address, false),
275 AccountMeta::new_readonly(protocol_address, false),
276 ],
277 data: SetPrimaryProtocol { protocol_id }.to_bytes(),
278 }
279}
280
281pub fn set_paused(signer: Pubkey, paused: bool) -> Instruction {
283 let vault_address = vault_pda().0;
284
285 Instruction {
286 program_id: crate::ID,
287 accounts: vec![
288 AccountMeta::new(signer, true),
289 AccountMeta::new(vault_address, false),
290 ],
291 data: SetPaused {
292 paused: if paused { 1 } else { 0 },
293 }
294 .to_bytes(),
295 }
296}
297
298pub fn collect_platform_fee(
301 signer: Pubkey,
302 treasury: Pubkey,
303 stable_address: Pubkey,
304 protocol_id: u8,
305) -> Instruction {
306 let vault_address = vault_pda().0;
307 let vault_usdc_address = get_associated_token_address(&vault_address, &USDC_ADDRESS);
308 let protocol_address = protocol_pda(protocol_id).0;
309 let position_address = position_pda(stable_address, protocol_id).0;
310
311 let mut accounts = vec![
312 AccountMeta::new(signer, true),
313 AccountMeta::new(vault_address, false),
314 AccountMeta::new(treasury, false),
315 AccountMeta::new(vault_usdc_address, false),
316 AccountMeta::new_readonly(stable_address, false),
317 AccountMeta::new(protocol_address, false),
318 AccountMeta::new(position_address, false),
319 AccountMeta::new_readonly(spl_token::ID, false),
320 ];
321
322 accounts.extend(protocol_accounts(protocol_id, vault_address));
324
325 Instruction {
326 program_id: crate::ID,
327 accounts,
328 data: CollectPlatformFee { protocol_id }.to_bytes(),
329 }
330}
331
332pub fn set_treasury(signer: Pubkey, treasury: Pubkey) -> Instruction {
334 let vault_address = vault_pda().0;
335
336 Instruction {
337 program_id: crate::ID,
338 accounts: vec![
339 AccountMeta::new(signer, true),
340 AccountMeta::new(vault_address, false),
341 AccountMeta::new_readonly(treasury, false),
342 ],
343 data: SetTreasury {
344 treasury: treasury.to_bytes(),
345 }
346 .to_bytes(),
347 }
348}
349
350pub fn delete_vault(signer: Pubkey) -> Instruction {
353 let vault_address = vault_pda().0;
354
355 Instruction {
356 program_id: crate::ID,
357 accounts: vec![
358 AccountMeta::new(signer, true),
359 AccountMeta::new(vault_address, false),
360 ],
361 data: DeleteVault {}.to_bytes(),
362 }
363}
364
365pub fn create(
371 signer: Pubkey,
372 payer: Pubkey,
373 id: String,
374 name: String,
375 symbol: String,
376 noise: [u8; 32],
377) -> Instruction {
378 let id_bytes = id.as_bytes();
380 if id_bytes.len() > 32 {
381 panic!("Id must be at most 32 bytes");
382 }
383 let mut id_array = [0u8; 32];
384 id_array[..id_bytes.len()].copy_from_slice(id_bytes);
385
386 let symbol_bytes = symbol.as_bytes();
388 if symbol_bytes.len() > 32 {
389 panic!("Symbol must be at most 32 bytes");
390 }
391 let mut symbol_array = [0u8; 32];
392 symbol_array[..symbol_bytes.len()].copy_from_slice(symbol_bytes);
393
394 let name_bytes = name.as_bytes();
396 if name_bytes.len() > 32 {
397 panic!("Name must be at most 32 bytes");
398 }
399 let mut name_array = [0u8; 32];
400 name_array[..name_bytes.len()].copy_from_slice(name_bytes);
401
402 let mint_pda =
403 Pubkey::find_program_address(&[MINT, noise.as_ref()], &crate::ID.to_bytes().into());
404
405 let metadata_address = mpl_token_metadata::accounts::Metadata::find_pda(&mint_pda.0).0;
406 let stable_address = stable_pda(mint_pda.0).0;
407
408 Instruction {
409 program_id: crate::ID,
410 accounts: vec![
411 AccountMeta::new(signer, true),
412 AccountMeta::new(payer, true),
413 AccountMeta::new(metadata_address, false),
414 AccountMeta::new(stable_address, false),
415 AccountMeta::new(mint_pda.0, false),
416 AccountMeta::new_readonly(system_program::ID, false),
417 AccountMeta::new_readonly(spl_token::ID, false),
418 AccountMeta::new_readonly(spl_associated_token_account::ID, false),
419 AccountMeta::new_readonly(mpl_token_metadata::ID, false),
420 AccountMeta::new_readonly(sysvar::rent::ID, false),
421 ],
422 data: Create {
423 id: id_array,
424 name: name_array,
425 symbol: symbol_array,
426 noise,
427 bump: mint_pda.1,
428 }
429 .to_bytes(),
430 }
431}
432
433pub fn update_authority(signer: Pubkey, mint_stable: Pubkey, new_authority: Pubkey) -> Instruction {
435 let stable_address = stable_pda(mint_stable).0;
436
437 Instruction {
438 program_id: crate::ID,
439 accounts: vec![
440 AccountMeta::new(signer, true),
441 AccountMeta::new(stable_address, false),
442 ],
443 data: UpdateAuthority {
444 new_authority: new_authority.to_bytes(),
445 }
446 .to_bytes(),
447 }
448}
449
450pub fn claim(signer: Pubkey, mint_stable: Pubkey, protocol_id: u8, amount: u64, min_usdc_out: u64) -> Instruction {
452 let stable_address = stable_pda(mint_stable).0;
453 let signer_stable_address = get_associated_token_address(&signer, &mint_stable);
454 let signer_usdc_address = get_associated_token_address(&signer, &USDC_ADDRESS);
455 let vault_address = vault_pda().0;
456 let vault_usdc_address = get_associated_token_address(&vault_address, &USDC_ADDRESS);
457 let protocol_address = protocol_pda(protocol_id).0;
458 let position_address = position_pda(stable_address, protocol_id).0;
459
460 let mut accounts = vec![
461 AccountMeta::new(signer, true),
462 AccountMeta::new(signer, true),
463 AccountMeta::new(signer_stable_address, false),
464 AccountMeta::new(signer_usdc_address, false),
465 AccountMeta::new_readonly(USDC_ADDRESS, false),
466 AccountMeta::new(mint_stable, false),
467 AccountMeta::new(stable_address, false),
468 AccountMeta::new(vault_address, false),
469 AccountMeta::new(vault_usdc_address, false),
470 AccountMeta::new(protocol_address, false),
471 AccountMeta::new(position_address, false),
472 AccountMeta::new_readonly(system_program::ID, false),
473 AccountMeta::new_readonly(spl_token::ID, false),
474 AccountMeta::new_readonly(spl_associated_token_account::ID, false),
475 AccountMeta::new_readonly(sysvar::instructions::ID, false),
476 ];
477
478 accounts.extend(protocol_accounts(protocol_id, vault_address));
480
481 Instruction {
482 program_id: crate::ID,
483 accounts,
484 data: Claim {
485 amount: amount.to_le_bytes(),
486 min_usdc_out: min_usdc_out.to_le_bytes(),
487 }
488 .to_bytes(),
489 }
490}
491
492pub fn wrap(signer: Pubkey, mint_stable: Pubkey, protocol_id: u8, amount: u64) -> Instruction {
499 let stable_address = stable_pda(mint_stable).0;
500 let signer_stable_address = get_associated_token_address(&signer, &mint_stable);
501 let signer_usdc_address = get_associated_token_address(&signer, &USDC_ADDRESS);
502 let vault_address = vault_pda().0;
503 let vault_usdc_address = get_associated_token_address(&vault_address, &USDC_ADDRESS);
504 let protocol_address = protocol_pda(protocol_id).0;
505 let position_address = position_pda(stable_address, protocol_id).0;
506
507 let mut accounts = vec![
508 AccountMeta::new(signer, true),
509 AccountMeta::new(signer, true),
510 AccountMeta::new(signer_stable_address, false),
511 AccountMeta::new(signer_usdc_address, false),
512 AccountMeta::new_readonly(USDC_ADDRESS, false),
513 AccountMeta::new(mint_stable, false),
514 AccountMeta::new(stable_address, false),
515 AccountMeta::new(vault_address, false),
516 AccountMeta::new(vault_usdc_address, false),
517 AccountMeta::new(protocol_address, false),
518 AccountMeta::new(position_address, false),
519 AccountMeta::new_readonly(system_program::ID, false),
520 AccountMeta::new_readonly(spl_token::ID, false),
521 AccountMeta::new_readonly(spl_associated_token_account::ID, false),
522 AccountMeta::new_readonly(sysvar::instructions::ID, false),
523 ];
524
525 accounts.extend(protocol_accounts(protocol_id, vault_address));
527
528 Instruction {
529 program_id: crate::ID,
530 accounts,
531 data: Wrap {
532 amount: amount.to_le_bytes(),
533 }
534 .to_bytes(),
535 }
536}
537
538pub fn unwrap(signer: Pubkey, mint_stable: Pubkey, protocol_id: u8, amount: u64, min_usdc_out: u64) -> Instruction {
542 let stable_address = stable_pda(mint_stable).0;
543 let signer_stable_address = get_associated_token_address(&signer, &mint_stable);
544 let signer_usdc_address = get_associated_token_address(&signer, &USDC_ADDRESS);
545 let vault_address = vault_pda().0;
546 let vault_usdc_address = get_associated_token_address(&vault_address, &USDC_ADDRESS);
547 let protocol_address = protocol_pda(protocol_id).0;
548 let position_address = position_pda(stable_address, protocol_id).0;
549
550 let mut accounts = vec![
551 AccountMeta::new(signer, true),
552 AccountMeta::new(signer, true),
553 AccountMeta::new(signer_stable_address, false),
554 AccountMeta::new(signer_usdc_address, false),
555 AccountMeta::new_readonly(USDC_ADDRESS, false),
556 AccountMeta::new(mint_stable, false),
557 AccountMeta::new(stable_address, false),
558 AccountMeta::new(vault_address, false),
559 AccountMeta::new(vault_usdc_address, false),
560 AccountMeta::new(protocol_address, false),
561 AccountMeta::new(position_address, false),
562 AccountMeta::new_readonly(system_program::ID, false),
563 AccountMeta::new_readonly(spl_token::ID, false),
564 AccountMeta::new_readonly(spl_associated_token_account::ID, false),
565 AccountMeta::new_readonly(sysvar::instructions::ID, false),
566 ];
567
568 accounts.extend(protocol_accounts(protocol_id, vault_address));
570
571 Instruction {
572 program_id: crate::ID,
573 accounts,
574 data: Unwrap {
575 amount: amount.to_le_bytes(),
576 min_usdc_out: min_usdc_out.to_le_bytes(),
577 }
578 .to_bytes(),
579 }
580}
581
582pub struct ProtocolAllocation {
588 pub protocol_id: u8,
590 pub usdc_available: u64,
592}
593
594pub fn build_unwrap_bundle(
621 _signer: Pubkey,
622 _mint_stable: Pubkey,
623 _amount: u64,
624 _protocols: &[ProtocolAllocation],
625) -> Vec<Instruction> {
626 todo!("Implement multi-protocol unwrap bundle builder")
639}