1#![allow(clippy::too_many_arguments)]
4
5use {
6 crate::{
7 find_default_deposit_account_address_and_seed, find_pool_address, find_pool_mint_address,
8 find_pool_mint_authority_address, find_pool_mpl_authority_address,
9 find_pool_onramp_address, find_pool_stake_address, find_pool_stake_authority_address,
10 inline_mpl_token_metadata::{self, pda::find_metadata_account},
11 state::SinglePool,
12 },
13 borsh::{BorshDeserialize, BorshSerialize},
14 solana_instruction::{AccountMeta, Instruction},
15 solana_program_pack::Pack,
16 solana_pubkey::Pubkey,
17 solana_rent::Rent,
18 solana_stake_interface as stake,
19 solana_system_interface::{instruction as system_instruction, program as system_program},
20 solana_sysvar as sysvar,
21};
22
23#[repr(C)]
25#[derive(Clone, Debug, PartialEq, BorshSerialize, BorshDeserialize)]
26pub enum SinglePoolInstruction {
27 InitializePool,
46
47 ReplenishPool,
79
80 DepositStake,
98
99 WithdrawStake {
113 user_stake_authority: Pubkey,
115 token_amount: u64,
117 },
118
119 CreateTokenMetadata,
134
135 UpdateTokenMetadata {
145 name: String,
147 symbol: String,
149 uri: String,
151 },
152
153 InitializePoolOnRamp,
172}
173
174pub fn initialize(
176 program_id: &Pubkey,
177 vote_account_address: &Pubkey,
178 payer: &Pubkey,
179 rent: &Rent,
180 minimum_pool_balance: u64,
181) -> Vec<Instruction> {
182 let pool_address = find_pool_address(program_id, vote_account_address);
183 let pool_rent = rent.minimum_balance(std::mem::size_of::<SinglePool>());
184
185 let stake_address = find_pool_stake_address(program_id, &pool_address);
186 let onramp_address = find_pool_onramp_address(program_id, &pool_address);
187 let stake_space = std::mem::size_of::<stake::state::StakeStateV2>();
188 let stake_rent = rent.minimum_balance(stake_space);
189 let stake_rent_plus_minimum = stake_rent.saturating_add(minimum_pool_balance);
190
191 let mint_address = find_pool_mint_address(program_id, &pool_address);
192 let mint_rent = rent.minimum_balance(spl_token::state::Mint::LEN);
193
194 vec![
195 system_instruction::transfer(payer, &pool_address, pool_rent),
196 system_instruction::transfer(payer, &stake_address, stake_rent_plus_minimum),
197 system_instruction::transfer(payer, &onramp_address, stake_rent),
198 system_instruction::transfer(payer, &mint_address, mint_rent),
199 initialize_pool(program_id, vote_account_address),
200 initialize_pool_onramp(program_id, &pool_address),
201 create_token_metadata(program_id, &pool_address, payer),
202 ]
203}
204
205pub fn initialize_pool(program_id: &Pubkey, vote_account_address: &Pubkey) -> Instruction {
207 let pool_address = find_pool_address(program_id, vote_account_address);
208 let mint_address = find_pool_mint_address(program_id, &pool_address);
209
210 let data = borsh::to_vec(&SinglePoolInstruction::InitializePool).unwrap();
211 let accounts = vec![
212 AccountMeta::new_readonly(*vote_account_address, false),
213 AccountMeta::new(pool_address, false),
214 AccountMeta::new(find_pool_stake_address(program_id, &pool_address), false),
215 AccountMeta::new(mint_address, false),
216 AccountMeta::new_readonly(
217 find_pool_stake_authority_address(program_id, &pool_address),
218 false,
219 ),
220 AccountMeta::new_readonly(
221 find_pool_mint_authority_address(program_id, &pool_address),
222 false,
223 ),
224 AccountMeta::new_readonly(sysvar::rent::id(), false),
225 AccountMeta::new_readonly(sysvar::clock::id(), false),
226 AccountMeta::new_readonly(sysvar::stake_history::id(), false),
227 #[allow(deprecated)]
228 AccountMeta::new_readonly(stake::config::id(), false),
229 AccountMeta::new_readonly(system_program::id(), false),
230 AccountMeta::new_readonly(spl_token::id(), false),
231 AccountMeta::new_readonly(stake::program::id(), false),
232 ];
233
234 Instruction {
235 program_id: *program_id,
236 accounts,
237 data,
238 }
239}
240
241pub fn replenish_pool(program_id: &Pubkey, vote_account_address: &Pubkey) -> Instruction {
243 let pool_address = find_pool_address(program_id, vote_account_address);
244
245 let data = borsh::to_vec(&SinglePoolInstruction::ReplenishPool).unwrap();
246 let accounts = vec![
247 AccountMeta::new_readonly(*vote_account_address, false),
248 AccountMeta::new_readonly(pool_address, false),
249 AccountMeta::new(find_pool_stake_address(program_id, &pool_address), false),
250 AccountMeta::new(find_pool_onramp_address(program_id, &pool_address), false),
251 AccountMeta::new_readonly(
252 find_pool_stake_authority_address(program_id, &pool_address),
253 false,
254 ),
255 AccountMeta::new_readonly(sysvar::clock::id(), false),
256 AccountMeta::new_readonly(sysvar::stake_history::id(), false),
257 #[allow(deprecated)]
258 AccountMeta::new_readonly(stake::config::id(), false),
259 AccountMeta::new_readonly(stake::program::id(), false),
260 ];
261
262 Instruction {
263 program_id: *program_id,
264 accounts,
265 data,
266 }
267}
268
269pub fn deposit(
271 program_id: &Pubkey,
272 pool_address: &Pubkey,
273 user_stake_account: &Pubkey,
274 user_token_account: &Pubkey,
275 user_lamport_account: &Pubkey,
276 user_withdraw_authority: &Pubkey,
277) -> Vec<Instruction> {
278 let pool_stake_authority = find_pool_stake_authority_address(program_id, pool_address);
279
280 vec![
281 stake::instruction::authorize(
282 user_stake_account,
283 user_withdraw_authority,
284 &pool_stake_authority,
285 stake::state::StakeAuthorize::Staker,
286 None,
287 ),
288 stake::instruction::authorize(
289 user_stake_account,
290 user_withdraw_authority,
291 &pool_stake_authority,
292 stake::state::StakeAuthorize::Withdrawer,
293 None,
294 ),
295 deposit_stake(
296 program_id,
297 pool_address,
298 user_stake_account,
299 user_token_account,
300 user_lamport_account,
301 ),
302 ]
303}
304
305pub fn deposit_stake(
307 program_id: &Pubkey,
308 pool_address: &Pubkey,
309 user_stake_account: &Pubkey,
310 user_token_account: &Pubkey,
311 user_lamport_account: &Pubkey,
312) -> Instruction {
313 let data = borsh::to_vec(&SinglePoolInstruction::DepositStake).unwrap();
314
315 let accounts = vec![
316 AccountMeta::new_readonly(*pool_address, false),
317 AccountMeta::new(find_pool_stake_address(program_id, pool_address), false),
318 AccountMeta::new(find_pool_mint_address(program_id, pool_address), false),
319 AccountMeta::new_readonly(
320 find_pool_stake_authority_address(program_id, pool_address),
321 false,
322 ),
323 AccountMeta::new_readonly(
324 find_pool_mint_authority_address(program_id, pool_address),
325 false,
326 ),
327 AccountMeta::new(*user_stake_account, false),
328 AccountMeta::new(*user_token_account, false),
329 AccountMeta::new(*user_lamport_account, false),
330 AccountMeta::new_readonly(sysvar::clock::id(), false),
331 AccountMeta::new_readonly(sysvar::stake_history::id(), false),
332 AccountMeta::new_readonly(spl_token::id(), false),
333 AccountMeta::new_readonly(stake::program::id(), false),
334 ];
335
336 Instruction {
337 program_id: *program_id,
338 accounts,
339 data,
340 }
341}
342
343pub fn withdraw(
349 program_id: &Pubkey,
350 pool_address: &Pubkey,
351 user_stake_account: &Pubkey,
352 user_stake_authority: &Pubkey,
353 user_token_account: &Pubkey,
354 user_token_authority: &Pubkey,
355 token_amount: u64,
356) -> Vec<Instruction> {
357 vec![
358 spl_token::instruction::approve(
359 &spl_token::id(),
360 user_token_account,
361 &find_pool_mint_authority_address(program_id, pool_address),
362 user_token_authority,
363 &[],
364 token_amount,
365 )
366 .unwrap(),
367 withdraw_stake(
368 program_id,
369 pool_address,
370 user_stake_account,
371 user_stake_authority,
372 user_token_account,
373 token_amount,
374 ),
375 ]
376}
377
378pub fn withdraw_stake(
380 program_id: &Pubkey,
381 pool_address: &Pubkey,
382 user_stake_account: &Pubkey,
383 user_stake_authority: &Pubkey,
384 user_token_account: &Pubkey,
385 token_amount: u64,
386) -> Instruction {
387 let data = borsh::to_vec(&SinglePoolInstruction::WithdrawStake {
388 user_stake_authority: *user_stake_authority,
389 token_amount,
390 })
391 .unwrap();
392
393 let accounts = vec![
394 AccountMeta::new_readonly(*pool_address, false),
395 AccountMeta::new(find_pool_stake_address(program_id, pool_address), false),
396 AccountMeta::new(find_pool_mint_address(program_id, pool_address), false),
397 AccountMeta::new_readonly(
398 find_pool_stake_authority_address(program_id, pool_address),
399 false,
400 ),
401 AccountMeta::new_readonly(
402 find_pool_mint_authority_address(program_id, pool_address),
403 false,
404 ),
405 AccountMeta::new(*user_stake_account, false),
406 AccountMeta::new(*user_token_account, false),
407 AccountMeta::new_readonly(sysvar::clock::id(), false),
408 AccountMeta::new_readonly(spl_token::id(), false),
409 AccountMeta::new_readonly(stake::program::id(), false),
410 ];
411
412 Instruction {
413 program_id: *program_id,
414 accounts,
415 data,
416 }
417}
418
419#[deprecated(
425 since = "3.0.0",
426 note = "Default deposit helpers will be removed in a future release; these were \
427 intended to support a wallet flow that never materialized. To set up a new stake \
428 account for deposit, use `instruction::create_account_and_delegate_stake` from \
429 `solana-stake-interface` using any normal keypair."
430)]
431pub fn create_and_delegate_user_stake(
432 program_id: &Pubkey,
433 vote_account_address: &Pubkey,
434 user_wallet: &Pubkey,
435 rent: &Rent,
436 stake_amount: u64,
437) -> Vec<Instruction> {
438 let pool_address = find_pool_address(program_id, vote_account_address);
439 let stake_space = std::mem::size_of::<stake::state::StakeStateV2>();
440 let lamports = rent
441 .minimum_balance(stake_space)
442 .saturating_add(stake_amount);
443 let (deposit_address, deposit_seed) =
444 find_default_deposit_account_address_and_seed(&pool_address, user_wallet);
445
446 stake::instruction::create_account_with_seed_and_delegate_stake(
447 user_wallet,
448 &deposit_address,
449 user_wallet,
450 &deposit_seed,
451 vote_account_address,
452 &stake::state::Authorized::auto(user_wallet),
453 &stake::state::Lockup::default(),
454 lamports,
455 )
456}
457
458pub fn create_token_metadata(
460 program_id: &Pubkey,
461 pool_address: &Pubkey,
462 payer: &Pubkey,
463) -> Instruction {
464 let pool_mint = find_pool_mint_address(program_id, pool_address);
465 let (token_metadata, _) = find_metadata_account(&pool_mint);
466 let data = borsh::to_vec(&SinglePoolInstruction::CreateTokenMetadata).unwrap();
467
468 let accounts = vec![
469 AccountMeta::new_readonly(*pool_address, false),
470 AccountMeta::new_readonly(pool_mint, false),
471 AccountMeta::new_readonly(
472 find_pool_mint_authority_address(program_id, pool_address),
473 false,
474 ),
475 AccountMeta::new_readonly(
476 find_pool_mpl_authority_address(program_id, pool_address),
477 false,
478 ),
479 AccountMeta::new(*payer, true),
480 AccountMeta::new(token_metadata, false),
481 AccountMeta::new_readonly(inline_mpl_token_metadata::id(), false),
482 AccountMeta::new_readonly(system_program::id(), false),
483 ];
484
485 Instruction {
486 program_id: *program_id,
487 accounts,
488 data,
489 }
490}
491
492pub fn update_token_metadata(
494 program_id: &Pubkey,
495 vote_account_address: &Pubkey,
496 authorized_withdrawer: &Pubkey,
497 name: String,
498 symbol: String,
499 uri: String,
500) -> Instruction {
501 let pool_address = find_pool_address(program_id, vote_account_address);
502 let pool_mint = find_pool_mint_address(program_id, &pool_address);
503 let (token_metadata, _) = find_metadata_account(&pool_mint);
504 let data =
505 borsh::to_vec(&SinglePoolInstruction::UpdateTokenMetadata { name, symbol, uri }).unwrap();
506
507 let accounts = vec![
508 AccountMeta::new_readonly(*vote_account_address, false),
509 AccountMeta::new_readonly(pool_address, false),
510 AccountMeta::new_readonly(
511 find_pool_mpl_authority_address(program_id, &pool_address),
512 false,
513 ),
514 AccountMeta::new_readonly(*authorized_withdrawer, true),
515 AccountMeta::new(token_metadata, false),
516 AccountMeta::new_readonly(inline_mpl_token_metadata::id(), false),
517 ];
518
519 Instruction {
520 program_id: *program_id,
521 accounts,
522 data,
523 }
524}
525
526pub fn initialize_pool_onramp(program_id: &Pubkey, pool_address: &Pubkey) -> Instruction {
528 let data = borsh::to_vec(&SinglePoolInstruction::InitializePoolOnRamp).unwrap();
529 let accounts = vec![
530 AccountMeta::new_readonly(*pool_address, false),
531 AccountMeta::new(find_pool_onramp_address(program_id, pool_address), false),
532 AccountMeta::new_readonly(
533 find_pool_stake_authority_address(program_id, pool_address),
534 false,
535 ),
536 AccountMeta::new_readonly(sysvar::rent::id(), false),
537 AccountMeta::new_readonly(system_program::id(), false),
538 AccountMeta::new_readonly(stake::program::id(), false),
539 ];
540
541 Instruction {
542 program_id: *program_id,
543 accounts,
544 data,
545 }
546}
547
548pub fn create_pool_onramp(
552 program_id: &Pubkey,
553 pool_address: &Pubkey,
554 payer: &Pubkey,
555 rent: &Rent,
556) -> Vec<Instruction> {
557 let onramp_address = find_pool_onramp_address(program_id, pool_address);
558 let stake_space = std::mem::size_of::<stake::state::StakeStateV2>();
559 let stake_rent = rent.minimum_balance(stake_space);
560
561 vec![
562 system_instruction::transfer(payer, &onramp_address, stake_rent),
563 initialize_pool_onramp(program_id, pool_address),
564 ]
565}