Skip to main content

gmsol_sdk/builders/
token.rs

1use 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/// Prepare token accounts for the owner.
13#[cfg_attr(serde, derive(serde::Serialize, serde::Deserialize))]
14#[derive(Debug, Clone, TypedBuilder)]
15pub struct PrepareTokenAccounts {
16    /// Payer.
17    #[builder(setter(into))]
18    pub payer: StringPubkey,
19    /// Owner.
20    #[cfg_attr(serde, serde(default))]
21    #[builder(default, setter(strip_option, into))]
22    pub owner: Option<StringPubkey>,
23    /// Tokens.
24    #[builder(setter(into))]
25    pub tokens: HashSet<StringPubkey>,
26    /// Token Program ID.
27    #[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/// Wraps the native token into its corresponding associated token account
47#[cfg_attr(serde, derive(serde::Serialize, serde::Deserialize))]
48#[derive(Debug, Clone, TypedBuilder)]
49pub struct WrapNative {
50    /// Owner.
51    #[builder(setter(into))]
52    pub owner: StringPubkey,
53    /// Lamports.
54    pub lamports: u64,
55}
56
57impl WrapNative {
58    /// Native mint.
59    pub const NATIVE_MINT: Pubkey = native_mint::ID;
60}
61
62/// Whether to skip the ATA preparation.
63pub 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}