gmsol_sdk/builders/
token.rs1use std::collections::HashSet;
2
3use anchor_spl::token::spl_token::native_mint;
4use either::Either;
5use solana_sdk::pubkey::Pubkey;
6use typed_builder::TypedBuilder;
7
8use crate::{serde::StringPubkey, AtomicGroup, IntoAtomicGroup};
9
10use super::utils::prepare_ata;
11
12#[cfg_attr(serde, derive(serde::Serialize, serde::Deserialize))]
14#[derive(Debug, Clone, TypedBuilder)]
15pub struct PrepareTokenAccounts {
16 #[builder(setter(into))]
18 pub payer: StringPubkey,
19 #[cfg_attr(serde, serde(default))]
21 #[builder(default, setter(strip_option, into))]
22 pub owner: Option<StringPubkey>,
23 #[builder(setter(into))]
25 pub tokens: HashSet<StringPubkey>,
26 #[builder(default = StringPubkey(anchor_spl::token::ID), setter(into))]
28 pub token_program: StringPubkey,
29}
30
31impl IntoAtomicGroup for PrepareTokenAccounts {
32 type Hint = ();
33
34 fn into_atomic_group(self, _hint: &Self::Hint) -> Result<AtomicGroup, crate::SolanaUtilsError> {
35 let payer = self.payer.0;
36 let owner = self.owner.as_deref().copied().unwrap_or(payer);
37 let insts = self.tokens.iter().map(|token| {
38 prepare_ata(&payer, &owner, Some(token), &self.token_program)
39 .unwrap()
40 .1
41 });
42 Ok(AtomicGroup::with_instructions(&payer, insts))
43 }
44}
45
46#[cfg_attr(serde, derive(serde::Serialize, serde::Deserialize))]
48#[derive(Debug, Clone, TypedBuilder)]
49pub struct WrapNative {
50 #[builder(setter(into))]
52 pub owner: StringPubkey,
53 pub lamports: u64,
55}
56
57impl WrapNative {
58 pub const NATIVE_MINT: Pubkey = native_mint::ID;
60}
61
62pub type SkipPreparation = bool;
64
65impl IntoAtomicGroup for WrapNative {
66 type Hint = SkipPreparation;
67
68 fn into_atomic_group(
69 self,
70 skip_preparation: &Self::Hint,
71 ) -> Result<AtomicGroup, crate::SolanaUtilsError> {
72 use anchor_spl::token::{spl_token::instruction::sync_native, ID};
73 use gmsol_programs::anchor_lang::solana_program::system_instruction::transfer;
74
75 let owner = self.owner.0;
76 let (ata, prepare) = prepare_ata(&owner, &owner, Some(&Self::NATIVE_MINT), &ID).unwrap();
77 let transfer = transfer(&owner, &ata, self.lamports);
78 let sync = sync_native(&ID, &ata).unwrap();
79
80 Ok(AtomicGroup::with_instructions(
81 &owner,
82 if *skip_preparation {
83 Either::Left([transfer, sync].into_iter())
84 } else {
85 Either::Right([prepare, transfer, sync].into_iter())
86 },
87 ))
88 }
89}
90
91#[cfg(test)]
92mod tests {
93 use gmsol_solana_utils::transaction_builder::default_before_sign;
94 use solana_sdk::pubkey::Pubkey;
95
96 use super::*;
97
98 #[test]
99 fn prepare_token_accounts() {
100 let tokens = [Pubkey::new_unique().into(), Pubkey::new_unique().into()];
101 let insts = PrepareTokenAccounts::builder()
102 .payer(Pubkey::new_unique())
103 .tokens(tokens)
104 .build()
105 .into_atomic_group(&())
106 .unwrap();
107 assert_eq!(insts.len(), tokens.len());
108 insts
109 .partially_signed_transaction_with_blockhash_and_options(
110 Default::default(),
111 Default::default(),
112 None,
113 default_before_sign,
114 )
115 .unwrap();
116 }
117
118 #[test]
119 fn wrap_native() {
120 WrapNative::builder()
121 .owner(Pubkey::new_unique())
122 .lamports(1_000_000_000)
123 .build()
124 .into_atomic_group(&true)
125 .unwrap()
126 .partially_signed_transaction_with_blockhash_and_options(
127 Default::default(),
128 Default::default(),
129 None,
130 default_before_sign,
131 )
132 .unwrap();
133 }
134}