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_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(sysvar::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(
350 program_id: &Pubkey,
351 pool_address: &Pubkey,
352 user_stake_account: &Pubkey,
353 user_stake_authority: &Pubkey,
354 user_token_account: &Pubkey,
355 user_token_authority: &Pubkey,
356 token_amount: u64,
357) -> Vec<Instruction> {
358 vec![
359 spl_token::instruction::approve(
360 &spl_token::id(),
361 user_token_account,
362 &find_pool_mint_authority_address(program_id, pool_address),
363 user_token_authority,
364 &[],
365 token_amount,
366 )
367 .unwrap(),
368 withdraw_stake(
369 program_id,
370 pool_address,
371 user_stake_account,
372 user_stake_authority,
373 user_token_account,
374 token_amount,
375 ),
376 ]
377}
378
379pub fn withdraw_stake(
381 program_id: &Pubkey,
382 pool_address: &Pubkey,
383 user_stake_account: &Pubkey,
384 user_stake_authority: &Pubkey,
385 user_token_account: &Pubkey,
386 token_amount: u64,
387) -> Instruction {
388 let data = borsh::to_vec(&SinglePoolInstruction::WithdrawStake {
389 user_stake_authority: *user_stake_authority,
390 token_amount,
391 })
392 .unwrap();
393
394 let accounts = vec![
395 AccountMeta::new_readonly(*pool_address, false),
396 AccountMeta::new(find_pool_stake_address(program_id, pool_address), false),
397 AccountMeta::new_readonly(find_pool_onramp_address(program_id, pool_address), false),
398 AccountMeta::new(find_pool_mint_address(program_id, pool_address), false),
399 AccountMeta::new_readonly(
400 find_pool_stake_authority_address(program_id, pool_address),
401 false,
402 ),
403 AccountMeta::new_readonly(
404 find_pool_mint_authority_address(program_id, pool_address),
405 false,
406 ),
407 AccountMeta::new(*user_stake_account, false),
408 AccountMeta::new(*user_token_account, false),
409 AccountMeta::new_readonly(sysvar::clock::id(), false),
410 AccountMeta::new_readonly(spl_token::id(), false),
411 AccountMeta::new_readonly(stake::program::id(), false),
412 ];
413
414 Instruction {
415 program_id: *program_id,
416 accounts,
417 data,
418 }
419}
420
421#[deprecated(
427 since = "3.0.0",
428 note = "Default deposit helpers will be removed in a future release; these were \
429 intended to support a wallet flow that never materialized. To set up a new stake \
430 account for deposit, use `instruction::create_account_and_delegate_stake` from \
431 `solana-stake-interface` using any normal keypair."
432)]
433pub fn create_and_delegate_user_stake(
434 program_id: &Pubkey,
435 vote_account_address: &Pubkey,
436 user_wallet: &Pubkey,
437 rent: &Rent,
438 stake_amount: u64,
439) -> Vec<Instruction> {
440 let pool_address = find_pool_address(program_id, vote_account_address);
441 let stake_space = std::mem::size_of::<stake::state::StakeStateV2>();
442 let lamports = rent
443 .minimum_balance(stake_space)
444 .saturating_add(stake_amount);
445 let (deposit_address, deposit_seed) =
446 find_default_deposit_account_address_and_seed(&pool_address, user_wallet);
447
448 stake::instruction::create_account_with_seed_and_delegate_stake(
449 user_wallet,
450 &deposit_address,
451 user_wallet,
452 &deposit_seed,
453 vote_account_address,
454 &stake::state::Authorized::auto(user_wallet),
455 &stake::state::Lockup::default(),
456 lamports,
457 )
458}
459
460pub fn create_token_metadata(
462 program_id: &Pubkey,
463 pool_address: &Pubkey,
464 payer: &Pubkey,
465) -> Instruction {
466 let pool_mint = find_pool_mint_address(program_id, pool_address);
467 let (token_metadata, _) = find_metadata_account(&pool_mint);
468 let data = borsh::to_vec(&SinglePoolInstruction::CreateTokenMetadata).unwrap();
469
470 let accounts = vec![
471 AccountMeta::new_readonly(*pool_address, false),
472 AccountMeta::new_readonly(pool_mint, false),
473 AccountMeta::new_readonly(
474 find_pool_mint_authority_address(program_id, pool_address),
475 false,
476 ),
477 AccountMeta::new_readonly(
478 find_pool_mpl_authority_address(program_id, pool_address),
479 false,
480 ),
481 AccountMeta::new(*payer, true),
482 AccountMeta::new(token_metadata, false),
483 AccountMeta::new_readonly(inline_mpl_token_metadata::id(), false),
484 AccountMeta::new_readonly(system_program::id(), false),
485 ];
486
487 Instruction {
488 program_id: *program_id,
489 accounts,
490 data,
491 }
492}
493
494pub fn update_token_metadata(
496 program_id: &Pubkey,
497 vote_account_address: &Pubkey,
498 authorized_withdrawer: &Pubkey,
499 name: String,
500 symbol: String,
501 uri: String,
502) -> Instruction {
503 let pool_address = find_pool_address(program_id, vote_account_address);
504 let pool_mint = find_pool_mint_address(program_id, &pool_address);
505 let (token_metadata, _) = find_metadata_account(&pool_mint);
506 let data =
507 borsh::to_vec(&SinglePoolInstruction::UpdateTokenMetadata { name, symbol, uri }).unwrap();
508
509 let accounts = vec![
510 AccountMeta::new_readonly(*vote_account_address, false),
511 AccountMeta::new_readonly(pool_address, false),
512 AccountMeta::new_readonly(
513 find_pool_mpl_authority_address(program_id, &pool_address),
514 false,
515 ),
516 AccountMeta::new_readonly(*authorized_withdrawer, true),
517 AccountMeta::new(token_metadata, false),
518 AccountMeta::new_readonly(inline_mpl_token_metadata::id(), false),
519 ];
520
521 Instruction {
522 program_id: *program_id,
523 accounts,
524 data,
525 }
526}
527
528pub fn initialize_pool_onramp(program_id: &Pubkey, pool_address: &Pubkey) -> Instruction {
530 let data = borsh::to_vec(&SinglePoolInstruction::InitializePoolOnRamp).unwrap();
531 let accounts = vec![
532 AccountMeta::new_readonly(*pool_address, false),
533 AccountMeta::new(find_pool_onramp_address(program_id, pool_address), false),
534 AccountMeta::new_readonly(
535 find_pool_stake_authority_address(program_id, pool_address),
536 false,
537 ),
538 AccountMeta::new_readonly(sysvar::rent::id(), false),
539 AccountMeta::new_readonly(system_program::id(), false),
540 AccountMeta::new_readonly(stake::program::id(), false),
541 ];
542
543 Instruction {
544 program_id: *program_id,
545 accounts,
546 data,
547 }
548}
549
550pub fn create_pool_onramp(
554 program_id: &Pubkey,
555 pool_address: &Pubkey,
556 payer: &Pubkey,
557 rent: &Rent,
558) -> Vec<Instruction> {
559 let onramp_address = find_pool_onramp_address(program_id, pool_address);
560 let stake_space = std::mem::size_of::<stake::state::StakeStateV2>();
561 let stake_rent = rent.minimum_balance(stake_space);
562
563 vec![
564 system_instruction::transfer(payer, &onramp_address, stake_rent),
565 initialize_pool_onramp(program_id, pool_address),
566 ]
567}