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::{self as stake, sysvar::stake_history},
19 solana_system_interface::{instruction as system_instruction, program as system_program},
20 solana_sysvar as sysvar, spl_token_interface as spl_token,
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(SinglePool::size_of());
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 = stake::state::StakeStateV2::size_of();
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(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(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_readonly(find_pool_onramp_address(program_id, pool_address), false),
319 AccountMeta::new(find_pool_mint_address(program_id, pool_address), false),
320 AccountMeta::new_readonly(
321 find_pool_stake_authority_address(program_id, pool_address),
322 false,
323 ),
324 AccountMeta::new_readonly(
325 find_pool_mint_authority_address(program_id, pool_address),
326 false,
327 ),
328 AccountMeta::new(*user_stake_account, false),
329 AccountMeta::new(*user_token_account, false),
330 AccountMeta::new(*user_lamport_account, false),
331 AccountMeta::new_readonly(sysvar::clock::id(), false),
332 AccountMeta::new_readonly(stake_history::id(), false),
333 AccountMeta::new_readonly(spl_token::id(), false),
334 AccountMeta::new_readonly(stake::program::id(), false),
335 ];
336
337 Instruction {
338 program_id: *program_id,
339 accounts,
340 data,
341 }
342}
343
344pub 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_readonly(find_pool_onramp_address(program_id, pool_address), false),
397 AccountMeta::new(find_pool_mint_address(program_id, pool_address), false),
398 AccountMeta::new_readonly(
399 find_pool_stake_authority_address(program_id, pool_address),
400 false,
401 ),
402 AccountMeta::new_readonly(
403 find_pool_mint_authority_address(program_id, pool_address),
404 false,
405 ),
406 AccountMeta::new(*user_stake_account, false),
407 AccountMeta::new(*user_token_account, false),
408 AccountMeta::new_readonly(sysvar::clock::id(), false),
409 AccountMeta::new_readonly(spl_token::id(), false),
410 AccountMeta::new_readonly(stake::program::id(), false),
411 ];
412
413 Instruction {
414 program_id: *program_id,
415 accounts,
416 data,
417 }
418}
419
420#[deprecated(
426 since = "3.0.0",
427 note = "Default deposit helpers will be removed in a future release; these were \
428 intended to support a wallet flow that never materialized. To set up a new stake \
429 account for deposit, use `instruction::create_account_and_delegate_stake` from \
430 `solana-stake-interface` using any normal keypair."
431)]
432pub fn create_and_delegate_user_stake(
433 program_id: &Pubkey,
434 vote_account_address: &Pubkey,
435 user_wallet: &Pubkey,
436 rent: &Rent,
437 stake_amount: u64,
438) -> Vec<Instruction> {
439 let pool_address = find_pool_address(program_id, vote_account_address);
440 let stake_space = stake::state::StakeStateV2::size_of();
441 let lamports = rent
442 .minimum_balance(stake_space)
443 .saturating_add(stake_amount);
444 let (deposit_address, deposit_seed) =
445 find_default_deposit_account_address_and_seed(&pool_address, user_wallet);
446
447 stake::instruction::create_account_with_seed_and_delegate_stake(
448 user_wallet,
449 &deposit_address,
450 user_wallet,
451 &deposit_seed,
452 vote_account_address,
453 &stake::state::Authorized::auto(user_wallet),
454 &stake::state::Lockup::default(),
455 lamports,
456 )
457}
458
459pub fn create_token_metadata(
461 program_id: &Pubkey,
462 pool_address: &Pubkey,
463 payer: &Pubkey,
464) -> Instruction {
465 let pool_mint = find_pool_mint_address(program_id, pool_address);
466 let (token_metadata, _) = find_metadata_account(&pool_mint);
467 let data = borsh::to_vec(&SinglePoolInstruction::CreateTokenMetadata).unwrap();
468
469 let accounts = vec![
470 AccountMeta::new_readonly(*pool_address, false),
471 AccountMeta::new_readonly(pool_mint, false),
472 AccountMeta::new_readonly(
473 find_pool_mint_authority_address(program_id, pool_address),
474 false,
475 ),
476 AccountMeta::new_readonly(
477 find_pool_mpl_authority_address(program_id, pool_address),
478 false,
479 ),
480 AccountMeta::new(*payer, true),
481 AccountMeta::new(token_metadata, false),
482 AccountMeta::new_readonly(inline_mpl_token_metadata::id(), false),
483 AccountMeta::new_readonly(system_program::id(), false),
484 ];
485
486 Instruction {
487 program_id: *program_id,
488 accounts,
489 data,
490 }
491}
492
493pub fn update_token_metadata(
495 program_id: &Pubkey,
496 vote_account_address: &Pubkey,
497 authorized_withdrawer: &Pubkey,
498 name: String,
499 symbol: String,
500 uri: String,
501) -> Instruction {
502 let pool_address = find_pool_address(program_id, vote_account_address);
503 let pool_mint = find_pool_mint_address(program_id, &pool_address);
504 let (token_metadata, _) = find_metadata_account(&pool_mint);
505 let data =
506 borsh::to_vec(&SinglePoolInstruction::UpdateTokenMetadata { name, symbol, uri }).unwrap();
507
508 let accounts = vec![
509 AccountMeta::new_readonly(*vote_account_address, false),
510 AccountMeta::new_readonly(pool_address, false),
511 AccountMeta::new_readonly(
512 find_pool_mpl_authority_address(program_id, &pool_address),
513 false,
514 ),
515 AccountMeta::new_readonly(*authorized_withdrawer, true),
516 AccountMeta::new(token_metadata, false),
517 AccountMeta::new_readonly(inline_mpl_token_metadata::id(), false),
518 ];
519
520 Instruction {
521 program_id: *program_id,
522 accounts,
523 data,
524 }
525}
526
527pub fn initialize_pool_onramp(program_id: &Pubkey, pool_address: &Pubkey) -> Instruction {
529 let data = borsh::to_vec(&SinglePoolInstruction::InitializePoolOnRamp).unwrap();
530 let accounts = vec![
531 AccountMeta::new_readonly(*pool_address, false),
532 AccountMeta::new(find_pool_onramp_address(program_id, pool_address), false),
533 AccountMeta::new_readonly(
534 find_pool_stake_authority_address(program_id, pool_address),
535 false,
536 ),
537 AccountMeta::new_readonly(sysvar::rent::id(), false),
538 AccountMeta::new_readonly(system_program::id(), false),
539 AccountMeta::new_readonly(stake::program::id(), false),
540 ];
541
542 Instruction {
543 program_id: *program_id,
544 accounts,
545 data,
546 }
547}
548
549pub fn create_pool_onramp(
553 program_id: &Pubkey,
554 pool_address: &Pubkey,
555 payer: &Pubkey,
556 rent: &Rent,
557) -> Vec<Instruction> {
558 let onramp_address = find_pool_onramp_address(program_id, pool_address);
559 let stake_space = stake::state::StakeStateV2::size_of();
560 let stake_rent = rent.minimum_balance(stake_space);
561
562 vec![
563 system_instruction::transfer(payer, &onramp_address, stake_rent),
564 initialize_pool_onramp(program_id, pool_address),
565 ]
566}