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 log(signer: Pubkey, msg: &[u8]) -> Instruction {
95 let mut data = Log {}.to_bytes();
96 data.extend_from_slice(msg);
97 Instruction {
98 program_id: crate::ID,
99 accounts: vec![AccountMeta::new(signer, true)],
100 data,
101 }
102}
103
104pub fn program_log(accounts: &[AccountInfo], msg: &[u8]) -> Result<(), ProgramError> {
106 invoke_signed(
107 &log(*accounts[0].key, msg),
108 accounts,
109 &crate::ID,
110 &[VAULT],
111 )
112}
113
114pub fn init(signer: Pubkey, platform_fee_bps: u16, treasury: Pubkey) -> Instruction {
120 let vault_address = vault_pda().0;
121 let vault_usdc_address = get_associated_token_address(&vault_address, &USDC_ADDRESS);
122
123 Instruction {
124 program_id: crate::ID,
125 accounts: vec![
126 AccountMeta::new(signer, true),
127 AccountMeta::new_readonly(USDC_ADDRESS, false),
128 AccountMeta::new(vault_address, false),
129 AccountMeta::new(vault_usdc_address, false),
130 AccountMeta::new_readonly(system_program::ID, false),
131 AccountMeta::new_readonly(spl_token::ID, false),
132 AccountMeta::new_readonly(spl_associated_token_account::ID, false),
133 ],
134 data: Init {
135 platform_fee_bps: platform_fee_bps.to_le_bytes(),
136 _padding: [0u8; 6],
137 treasury: treasury.to_bytes(),
138 }
139 .to_bytes(),
140 }
141}
142
143pub fn init_protocol(signer: Pubkey, protocol_id: u8, name: &str, user_lookup_table: Option<Pubkey>) -> Instruction {
145 let vault_address = vault_pda().0;
146 let protocol_address = protocol_pda(protocol_id).0;
147
148 let receipt_token_mint = match protocol_id {
150 PROTOCOL_KAMINO => CTOKEN_ADDRESS,
151 PROTOCOL_PERENA => USD_STAR_MINT,
152 _ => panic!("Unknown protocol"),
153 };
154
155 let vault_receipt_token_address = get_associated_token_address(&vault_address, &receipt_token_mint);
156
157 let name_bytes = name.as_bytes();
159 let mut name_array = [0u8; 32];
160 let len = name_bytes.len().min(32);
161 name_array[..len].copy_from_slice(&name_bytes[..len]);
162
163 let mut accounts = vec![
164 AccountMeta::new(signer, true),
165 AccountMeta::new(vault_address, false),
166 AccountMeta::new(protocol_address, false),
167 AccountMeta::new_readonly(receipt_token_mint, false),
168 AccountMeta::new(vault_receipt_token_address, false),
169 AccountMeta::new_readonly(system_program::ID, false),
170 AccountMeta::new_readonly(spl_token::ID, false),
171 AccountMeta::new_readonly(spl_associated_token_account::ID, false),
172 ];
173
174 if protocol_id == PROTOCOL_KAMINO {
176 let user_metadata_address = Pubkey::find_program_address(
177 &[b"user_meta", vault_address.as_ref()],
178 &klend_sdk::programs::KAMINO_LENDING_ID.to_bytes().into(),
179 )
180 .0;
181
182 accounts.extend(vec![
183 AccountMeta::new(OBLIGATION_ADDRESS, false),
184 AccountMeta::new(LENDING_MARKET_ADDRESS, false),
185 AccountMeta::new_readonly(USDC_ADDRESS, false),
186 AccountMeta::new_readonly(USDC_ADDRESS, false),
187 AccountMeta::new(user_metadata_address, false),
188 AccountMeta::new(user_lookup_table.unwrap_or(signer), false),
189 AccountMeta::new(LENDING_MARKET_AUTHORITY_ADDRESS, false),
190 AccountMeta::new(RESERVE_ADDRESS, false),
191 AccountMeta::new(RESERVE_FARM_USER_STATE_ADDRESS, false),
192 AccountMeta::new(RESERVE_FARM_STATE_ADDRESS, false),
193 AccountMeta::new_readonly(KFARMS_PROGRAM_ID, false),
194 AccountMeta::new_readonly(KLEND_PROGRAM_ID, false),
195 AccountMeta::new_readonly(sysvar::rent::ID, false),
196 AccountMeta::new_readonly(system_program::ID, false),
197 ]);
198 }
199
200 Instruction {
201 program_id: crate::ID,
202 accounts,
203 data: InitProtocol {
204 protocol_id,
205 name: name_array,
206 }
207 .to_bytes(),
208 }
209}
210
211pub fn checkpoint(signer: Pubkey, protocol_id: u8) -> Instruction {
213 let vault_address = vault_pda().0;
214 let protocol_address = protocol_pda(protocol_id).0;
215
216 let mut accounts = vec![
217 AccountMeta::new(signer, true),
218 AccountMeta::new(vault_address, false),
219 AccountMeta::new(protocol_address, false),
220 ];
221
222 match protocol_id {
224 PROTOCOL_KAMINO => {
225 accounts.push(AccountMeta::new_readonly(RESERVE_ADDRESS, false));
226 }
227 PROTOCOL_PERENA => {
228 accounts.push(AccountMeta::new_readonly(PERENA_ORACLE_STATE, false));
229 }
230 _ => {}
231 }
232
233 Instruction {
234 program_id: crate::ID,
235 accounts,
236 data: Checkpoint { protocol_id }.to_bytes(),
237 }
238}
239
240pub fn rebalance(
246 signer: Pubkey,
247 stable_address: Pubkey,
248 from_protocol: u8,
249 to_protocol: u8,
250 amount: u64,
251) -> Instruction {
252 let vault_address = vault_pda().0;
253 let vault_usdc_address = get_associated_token_address(&vault_address, &USDC_ADDRESS);
254 let from_protocol_address = protocol_pda(from_protocol).0;
255 let to_protocol_address = protocol_pda(to_protocol).0;
256 let from_position_address = position_pda(stable_address, from_protocol).0;
257 let to_position_address = position_pda(stable_address, to_protocol).0;
258
259 let mut accounts = vec![
260 AccountMeta::new(signer, true),
261 AccountMeta::new(vault_address, false),
262 AccountMeta::new(stable_address, false),
263 AccountMeta::new(from_protocol_address, false),
264 AccountMeta::new(to_protocol_address, false),
265 AccountMeta::new(from_position_address, false),
266 AccountMeta::new(to_position_address, false),
267 AccountMeta::new(vault_usdc_address, false),
268 AccountMeta::new_readonly(system_program::ID, false),
269 AccountMeta::new_readonly(spl_token::ID, false),
270 ];
271
272 accounts.extend(protocol_accounts(from_protocol, vault_address));
274 accounts.extend(protocol_accounts(to_protocol, vault_address));
276
277 Instruction {
278 program_id: crate::ID,
279 accounts,
280 data: Rebalance {
281 from_protocol,
282 to_protocol,
283 _padding: [0u8; 6],
284 amount: amount.to_le_bytes(),
285 }
286 .to_bytes(),
287 }
288}
289
290pub fn set_primary_protocol(signer: Pubkey, protocol_id: u8) -> Instruction {
292 let vault_address = vault_pda().0;
293 let protocol_address = protocol_pda(protocol_id).0;
294
295 Instruction {
296 program_id: crate::ID,
297 accounts: vec![
298 AccountMeta::new(signer, true),
299 AccountMeta::new(vault_address, false),
300 AccountMeta::new_readonly(protocol_address, false),
301 ],
302 data: SetPrimaryProtocol { protocol_id }.to_bytes(),
303 }
304}
305
306pub fn set_paused(signer: Pubkey, paused: bool) -> Instruction {
308 let vault_address = vault_pda().0;
309
310 Instruction {
311 program_id: crate::ID,
312 accounts: vec![
313 AccountMeta::new(signer, true),
314 AccountMeta::new(vault_address, false),
315 ],
316 data: SetPaused {
317 paused: if paused { 1 } else { 0 },
318 }
319 .to_bytes(),
320 }
321}
322
323pub fn collect_platform_fee(
326 signer: Pubkey,
327 treasury: Pubkey,
328 stable_address: Pubkey,
329 protocol_id: u8,
330) -> Instruction {
331 let vault_address = vault_pda().0;
332 let vault_usdc_address = get_associated_token_address(&vault_address, &USDC_ADDRESS);
333 let protocol_address = protocol_pda(protocol_id).0;
334 let position_address = position_pda(stable_address, protocol_id).0;
335
336 let mut accounts = vec![
337 AccountMeta::new(signer, true),
338 AccountMeta::new(vault_address, false),
339 AccountMeta::new(treasury, false),
340 AccountMeta::new(vault_usdc_address, false),
341 AccountMeta::new_readonly(stable_address, false),
342 AccountMeta::new(protocol_address, false),
343 AccountMeta::new(position_address, false),
344 AccountMeta::new_readonly(spl_token::ID, false),
345 ];
346
347 accounts.extend(protocol_accounts(protocol_id, vault_address));
349
350 Instruction {
351 program_id: crate::ID,
352 accounts,
353 data: CollectPlatformFee { protocol_id }.to_bytes(),
354 }
355}
356
357pub fn set_treasury(signer: Pubkey, treasury: Pubkey) -> Instruction {
359 let vault_address = vault_pda().0;
360
361 Instruction {
362 program_id: crate::ID,
363 accounts: vec![
364 AccountMeta::new(signer, true),
365 AccountMeta::new(vault_address, false),
366 AccountMeta::new_readonly(treasury, false),
367 ],
368 data: SetTreasury {
369 treasury: treasury.to_bytes(),
370 }
371 .to_bytes(),
372 }
373}
374
375pub fn delete_vault(signer: Pubkey) -> Instruction {
378 let vault_address = vault_pda().0;
379
380 Instruction {
381 program_id: crate::ID,
382 accounts: vec![
383 AccountMeta::new(signer, true),
384 AccountMeta::new(vault_address, false),
385 ],
386 data: DeleteVault {}.to_bytes(),
387 }
388}
389
390pub fn create(
396 signer: Pubkey,
397 payer: Pubkey,
398 id: String,
399 name: String,
400 symbol: String,
401 noise: [u8; 32],
402) -> Instruction {
403 let id_bytes = id.as_bytes();
405 if id_bytes.len() > 32 {
406 panic!("Id must be at most 32 bytes");
407 }
408 let mut id_array = [0u8; 32];
409 id_array[..id_bytes.len()].copy_from_slice(id_bytes);
410
411 let symbol_bytes = symbol.as_bytes();
413 if symbol_bytes.len() > 32 {
414 panic!("Symbol must be at most 32 bytes");
415 }
416 let mut symbol_array = [0u8; 32];
417 symbol_array[..symbol_bytes.len()].copy_from_slice(symbol_bytes);
418
419 let name_bytes = name.as_bytes();
421 if name_bytes.len() > 32 {
422 panic!("Name must be at most 32 bytes");
423 }
424 let mut name_array = [0u8; 32];
425 name_array[..name_bytes.len()].copy_from_slice(name_bytes);
426
427 let mint_pda =
428 Pubkey::find_program_address(&[MINT, noise.as_ref()], &crate::ID.to_bytes().into());
429
430 let metadata_address = mpl_token_metadata::accounts::Metadata::find_pda(&mint_pda.0).0;
431 let stable_address = stable_pda(mint_pda.0).0;
432
433 Instruction {
434 program_id: crate::ID,
435 accounts: vec![
436 AccountMeta::new(signer, true),
437 AccountMeta::new(payer, true),
438 AccountMeta::new(metadata_address, false),
439 AccountMeta::new(stable_address, false),
440 AccountMeta::new(mint_pda.0, false),
441 AccountMeta::new_readonly(system_program::ID, false),
442 AccountMeta::new_readonly(spl_token::ID, false),
443 AccountMeta::new_readonly(spl_associated_token_account::ID, false),
444 AccountMeta::new_readonly(mpl_token_metadata::ID, false),
445 AccountMeta::new_readonly(sysvar::rent::ID, false),
446 ],
447 data: Create {
448 id: id_array,
449 name: name_array,
450 symbol: symbol_array,
451 noise,
452 bump: mint_pda.1,
453 }
454 .to_bytes(),
455 }
456}
457
458pub fn update_authority(signer: Pubkey, mint_stable: Pubkey, new_authority: Pubkey) -> Instruction {
460 let stable_address = stable_pda(mint_stable).0;
461
462 Instruction {
463 program_id: crate::ID,
464 accounts: vec![
465 AccountMeta::new(signer, true),
466 AccountMeta::new(stable_address, false),
467 ],
468 data: UpdateAuthority {
469 new_authority: new_authority.to_bytes(),
470 }
471 .to_bytes(),
472 }
473}
474
475pub fn claim(signer: Pubkey, mint_stable: Pubkey, protocol_id: u8, amount: u64, min_usdc_out: u64) -> Instruction {
477 let stable_address = stable_pda(mint_stable).0;
478 let signer_stable_address = get_associated_token_address(&signer, &mint_stable);
479 let signer_usdc_address = get_associated_token_address(&signer, &USDC_ADDRESS);
480 let vault_address = vault_pda().0;
481 let vault_usdc_address = get_associated_token_address(&vault_address, &USDC_ADDRESS);
482 let protocol_address = protocol_pda(protocol_id).0;
483 let position_address = position_pda(stable_address, protocol_id).0;
484
485 let mut accounts = vec![
486 AccountMeta::new(signer, true),
487 AccountMeta::new(signer, true),
488 AccountMeta::new(signer_stable_address, false),
489 AccountMeta::new(signer_usdc_address, false),
490 AccountMeta::new_readonly(USDC_ADDRESS, false),
491 AccountMeta::new(mint_stable, false),
492 AccountMeta::new(stable_address, false),
493 AccountMeta::new(vault_address, false),
494 AccountMeta::new(vault_usdc_address, false),
495 AccountMeta::new(protocol_address, false),
496 AccountMeta::new(position_address, false),
497 AccountMeta::new_readonly(system_program::ID, false),
498 AccountMeta::new_readonly(spl_token::ID, false),
499 AccountMeta::new_readonly(spl_associated_token_account::ID, false),
500 AccountMeta::new_readonly(sysvar::instructions::ID, false),
501 AccountMeta::new_readonly(crate::ID, false),
502 ];
503
504 accounts.extend(protocol_accounts(protocol_id, vault_address));
506
507 Instruction {
508 program_id: crate::ID,
509 accounts,
510 data: Claim {
511 amount: amount.to_le_bytes(),
512 min_usdc_out: min_usdc_out.to_le_bytes(),
513 }
514 .to_bytes(),
515 }
516}
517
518pub fn wrap(signer: Pubkey, mint_stable: Pubkey, protocol_id: u8, amount: u64) -> Instruction {
525 let stable_address = stable_pda(mint_stable).0;
526 let signer_stable_address = get_associated_token_address(&signer, &mint_stable);
527 let signer_usdc_address = get_associated_token_address(&signer, &USDC_ADDRESS);
528 let vault_address = vault_pda().0;
529 let vault_usdc_address = get_associated_token_address(&vault_address, &USDC_ADDRESS);
530 let protocol_address = protocol_pda(protocol_id).0;
531 let position_address = position_pda(stable_address, protocol_id).0;
532
533 let mut accounts = vec![
534 AccountMeta::new(signer, true),
535 AccountMeta::new(signer, true),
536 AccountMeta::new(signer_stable_address, false),
537 AccountMeta::new(signer_usdc_address, false),
538 AccountMeta::new_readonly(USDC_ADDRESS, false),
539 AccountMeta::new(mint_stable, false),
540 AccountMeta::new(stable_address, false),
541 AccountMeta::new(vault_address, false),
542 AccountMeta::new(vault_usdc_address, false),
543 AccountMeta::new(protocol_address, false),
544 AccountMeta::new(position_address, false),
545 AccountMeta::new_readonly(system_program::ID, false),
546 AccountMeta::new_readonly(spl_token::ID, false),
547 AccountMeta::new_readonly(spl_associated_token_account::ID, false),
548 AccountMeta::new_readonly(sysvar::instructions::ID, false),
549 AccountMeta::new_readonly(crate::ID, false),
550 ];
551
552 accounts.extend(protocol_accounts(protocol_id, vault_address));
554
555 Instruction {
556 program_id: crate::ID,
557 accounts,
558 data: Wrap {
559 amount: amount.to_le_bytes(),
560 }
561 .to_bytes(),
562 }
563}
564
565pub fn unwrap(signer: Pubkey, mint_stable: Pubkey, protocol_id: u8, amount: u64, min_usdc_out: u64) -> Instruction {
569 let stable_address = stable_pda(mint_stable).0;
570 let signer_stable_address = get_associated_token_address(&signer, &mint_stable);
571 let signer_usdc_address = get_associated_token_address(&signer, &USDC_ADDRESS);
572 let vault_address = vault_pda().0;
573 let vault_usdc_address = get_associated_token_address(&vault_address, &USDC_ADDRESS);
574 let protocol_address = protocol_pda(protocol_id).0;
575 let position_address = position_pda(stable_address, protocol_id).0;
576
577 let mut accounts = vec![
578 AccountMeta::new(signer, true),
579 AccountMeta::new(signer, true),
580 AccountMeta::new(signer_stable_address, false),
581 AccountMeta::new(signer_usdc_address, false),
582 AccountMeta::new_readonly(USDC_ADDRESS, false),
583 AccountMeta::new(mint_stable, false),
584 AccountMeta::new(stable_address, false),
585 AccountMeta::new(vault_address, false),
586 AccountMeta::new(vault_usdc_address, false),
587 AccountMeta::new(protocol_address, false),
588 AccountMeta::new(position_address, false),
589 AccountMeta::new_readonly(system_program::ID, false),
590 AccountMeta::new_readonly(spl_token::ID, false),
591 AccountMeta::new_readonly(spl_associated_token_account::ID, false),
592 AccountMeta::new_readonly(sysvar::instructions::ID, false),
593 AccountMeta::new_readonly(crate::ID, false),
594 ];
595
596 accounts.extend(protocol_accounts(protocol_id, vault_address));
598
599 Instruction {
600 program_id: crate::ID,
601 accounts,
602 data: Unwrap {
603 amount: amount.to_le_bytes(),
604 min_usdc_out: min_usdc_out.to_le_bytes(),
605 }
606 .to_bytes(),
607 }
608}
609
610pub struct ProtocolAllocation {
616 pub protocol_id: u8,
618 pub usdc_available: u64,
620}
621
622pub fn build_unwrap_bundle(
649 _signer: Pubkey,
650 _mint_stable: Pubkey,
651 _amount: u64,
652 _protocols: &[ProtocolAllocation],
653) -> Vec<Instruction> {
654 todo!("Implement multi-protocol unwrap bundle builder")
667}