spl_token_client/
token.rs

1use {
2    crate::client::{
3        ProgramClient, ProgramClientError, SendTransaction, SimulateTransaction, SimulationResult,
4    },
5    bytemuck::{bytes_of, Pod},
6    futures::future::join_all,
7    futures_util::TryFutureExt,
8    solana_program_test::tokio::time,
9    solana_sdk::{
10        account::Account as BaseAccount,
11        compute_budget::ComputeBudgetInstruction,
12        hash::Hash,
13        instruction::{AccountMeta, Instruction},
14        message::Message,
15        packet::PACKET_DATA_SIZE,
16        program_error::ProgramError,
17        program_pack::Pack,
18        pubkey::Pubkey,
19        signature::Signature,
20        signer::{signers::Signers, Signer, SignerError},
21        system_instruction,
22        transaction::Transaction,
23    },
24    spl_associated_token_account_client::{
25        address::get_associated_token_address_with_program_id,
26        instruction::{
27            create_associated_token_account, create_associated_token_account_idempotent,
28        },
29    },
30    spl_record::state::RecordData,
31    spl_token_2022::{
32        extension::{
33            confidential_mint_burn::{
34                self,
35                account_info::{BurnAccountInfo, SupplyAccountInfo},
36                ConfidentialMintBurn,
37            },
38            confidential_transfer::{
39                self,
40                account_info::{
41                    ApplyPendingBalanceAccountInfo, EmptyAccountAccountInfo, TransferAccountInfo,
42                    WithdrawAccountInfo,
43                },
44                ConfidentialTransferAccount, DecryptableBalance,
45            },
46            confidential_transfer_fee::{
47                self, account_info::WithheldTokensInfo, ConfidentialTransferFeeAmount,
48                ConfidentialTransferFeeConfig,
49            },
50            cpi_guard, default_account_state, group_member_pointer, group_pointer,
51            interest_bearing_mint, memo_transfer, metadata_pointer, pausable, scaled_ui_amount,
52            transfer_fee, transfer_hook, BaseStateWithExtensions, Extension, ExtensionType,
53            StateWithExtensionsOwned,
54        },
55        instruction, offchain,
56        solana_zk_sdk::{
57            encryption::{
58                auth_encryption::AeKey,
59                elgamal::{ElGamalCiphertext, ElGamalKeypair, ElGamalPubkey, ElGamalSecretKey},
60                pod::{
61                    auth_encryption::PodAeCiphertext,
62                    elgamal::{PodElGamalCiphertext, PodElGamalPubkey},
63                },
64            },
65            zk_elgamal_proof_program::{
66                self,
67                instruction::{close_context_state, ContextStateInfo},
68                proof_data::*,
69                state::ProofContextState,
70            },
71        },
72        state::{Account, AccountState, Mint, Multisig},
73    },
74    spl_token_confidential_transfer_proof_extraction::instruction::{
75        zk_proof_type_to_instruction, ProofLocation,
76    },
77    spl_token_confidential_transfer_proof_generation::{
78        burn::BurnProofData, mint::MintProofData, transfer::TransferProofData,
79        transfer_with_fee::TransferWithFeeProofData, withdraw::WithdrawProofData,
80    },
81    spl_token_group_interface::state::{TokenGroup, TokenGroupMember},
82    spl_token_metadata_interface::state::{Field, TokenMetadata},
83    std::{
84        fmt, io,
85        mem::size_of,
86        sync::{Arc, RwLock},
87        time::{Duration, Instant},
88    },
89    thiserror::Error,
90};
91
92#[derive(Error, Debug)]
93pub enum TokenError {
94    #[error("client error: {0}")]
95    Client(ProgramClientError),
96    #[error("program error: {0}")]
97    Program(#[from] ProgramError),
98    #[error("account not found")]
99    AccountNotFound,
100    #[error("invalid account owner")]
101    AccountInvalidOwner,
102    #[error("invalid account mint")]
103    AccountInvalidMint,
104    #[error("invalid associated account address")]
105    AccountInvalidAssociatedAddress,
106    #[error("invalid auxiliary account address")]
107    AccountInvalidAuxiliaryAddress,
108    #[error("proof generation")]
109    ProofGeneration,
110    #[error("maximum deposit transfer amount exceeded")]
111    MaximumDepositTransferAmountExceeded,
112    #[error("encryption key error")]
113    Key(SignerError),
114    #[error("account decryption failed")]
115    AccountDecryption,
116    #[error("not enough funds in account")]
117    NotEnoughFunds,
118    #[error("missing memo signer")]
119    MissingMemoSigner,
120    #[error("decimals required, but missing")]
121    MissingDecimals,
122    #[error("decimals specified, but incorrect")]
123    InvalidDecimals,
124}
125impl PartialEq for TokenError {
126    fn eq(&self, other: &Self) -> bool {
127        match (self, other) {
128            // TODO not great, but workable for tests
129            // currently missing: proof error, signer error
130            (Self::Client(ref a), Self::Client(ref b)) => a.to_string() == b.to_string(),
131            (Self::Program(ref a), Self::Program(ref b)) => a == b,
132            (Self::AccountNotFound, Self::AccountNotFound) => true,
133            (Self::AccountInvalidOwner, Self::AccountInvalidOwner) => true,
134            (Self::AccountInvalidMint, Self::AccountInvalidMint) => true,
135            (Self::AccountInvalidAssociatedAddress, Self::AccountInvalidAssociatedAddress) => true,
136            (Self::AccountInvalidAuxiliaryAddress, Self::AccountInvalidAuxiliaryAddress) => true,
137            (Self::ProofGeneration, Self::ProofGeneration) => true,
138            (
139                Self::MaximumDepositTransferAmountExceeded,
140                Self::MaximumDepositTransferAmountExceeded,
141            ) => true,
142            (Self::AccountDecryption, Self::AccountDecryption) => true,
143            (Self::NotEnoughFunds, Self::NotEnoughFunds) => true,
144            (Self::MissingMemoSigner, Self::MissingMemoSigner) => true,
145            (Self::MissingDecimals, Self::MissingDecimals) => true,
146            (Self::InvalidDecimals, Self::InvalidDecimals) => true,
147            _ => false,
148        }
149    }
150}
151
152/// Encapsulates initializing an extension
153#[derive(Clone, Debug, PartialEq)]
154pub enum ExtensionInitializationParams {
155    ConfidentialTransferMint {
156        authority: Option<Pubkey>,
157        auto_approve_new_accounts: bool,
158        auditor_elgamal_pubkey: Option<PodElGamalPubkey>,
159    },
160    DefaultAccountState {
161        state: AccountState,
162    },
163    MintCloseAuthority {
164        close_authority: Option<Pubkey>,
165    },
166    TransferFeeConfig {
167        transfer_fee_config_authority: Option<Pubkey>,
168        withdraw_withheld_authority: Option<Pubkey>,
169        transfer_fee_basis_points: u16,
170        maximum_fee: u64,
171    },
172    InterestBearingConfig {
173        rate_authority: Option<Pubkey>,
174        rate: i16,
175    },
176    NonTransferable,
177    PermanentDelegate {
178        delegate: Pubkey,
179    },
180    TransferHook {
181        authority: Option<Pubkey>,
182        program_id: Option<Pubkey>,
183    },
184    MetadataPointer {
185        authority: Option<Pubkey>,
186        metadata_address: Option<Pubkey>,
187    },
188    ConfidentialTransferFeeConfig {
189        authority: Option<Pubkey>,
190        withdraw_withheld_authority_elgamal_pubkey: PodElGamalPubkey,
191    },
192    GroupPointer {
193        authority: Option<Pubkey>,
194        group_address: Option<Pubkey>,
195    },
196    GroupMemberPointer {
197        authority: Option<Pubkey>,
198        member_address: Option<Pubkey>,
199    },
200    ScaledUiAmountConfig {
201        authority: Option<Pubkey>,
202        multiplier: f64,
203    },
204    PausableConfig {
205        authority: Pubkey,
206    },
207    ConfidentialMintBurn {
208        supply_elgamal_pubkey: PodElGamalPubkey,
209        decryptable_supply: PodAeCiphertext,
210    },
211}
212impl ExtensionInitializationParams {
213    /// Get the extension type associated with the init params
214    pub fn extension(&self) -> ExtensionType {
215        match self {
216            Self::ConfidentialTransferMint { .. } => ExtensionType::ConfidentialTransferMint,
217            Self::DefaultAccountState { .. } => ExtensionType::DefaultAccountState,
218            Self::MintCloseAuthority { .. } => ExtensionType::MintCloseAuthority,
219            Self::TransferFeeConfig { .. } => ExtensionType::TransferFeeConfig,
220            Self::InterestBearingConfig { .. } => ExtensionType::InterestBearingConfig,
221            Self::NonTransferable => ExtensionType::NonTransferable,
222            Self::PermanentDelegate { .. } => ExtensionType::PermanentDelegate,
223            Self::TransferHook { .. } => ExtensionType::TransferHook,
224            Self::MetadataPointer { .. } => ExtensionType::MetadataPointer,
225            Self::ConfidentialTransferFeeConfig { .. } => {
226                ExtensionType::ConfidentialTransferFeeConfig
227            }
228            Self::GroupPointer { .. } => ExtensionType::GroupPointer,
229            Self::GroupMemberPointer { .. } => ExtensionType::GroupMemberPointer,
230            Self::ScaledUiAmountConfig { .. } => ExtensionType::ScaledUiAmount,
231            Self::PausableConfig { .. } => ExtensionType::Pausable,
232            Self::ConfidentialMintBurn { .. } => ExtensionType::ConfidentialMintBurn,
233        }
234    }
235    /// Generate an appropriate initialization instruction for the given mint
236    pub fn instruction(
237        self,
238        token_program_id: &Pubkey,
239        mint: &Pubkey,
240    ) -> Result<Instruction, ProgramError> {
241        match self {
242            Self::ConfidentialTransferMint {
243                authority,
244                auto_approve_new_accounts,
245                auditor_elgamal_pubkey,
246            } => confidential_transfer::instruction::initialize_mint(
247                token_program_id,
248                mint,
249                authority,
250                auto_approve_new_accounts,
251                auditor_elgamal_pubkey,
252            ),
253            Self::DefaultAccountState { state } => {
254                default_account_state::instruction::initialize_default_account_state(
255                    token_program_id,
256                    mint,
257                    &state,
258                )
259            }
260            Self::MintCloseAuthority { close_authority } => {
261                instruction::initialize_mint_close_authority(
262                    token_program_id,
263                    mint,
264                    close_authority.as_ref(),
265                )
266            }
267            Self::TransferFeeConfig {
268                transfer_fee_config_authority,
269                withdraw_withheld_authority,
270                transfer_fee_basis_points,
271                maximum_fee,
272            } => transfer_fee::instruction::initialize_transfer_fee_config(
273                token_program_id,
274                mint,
275                transfer_fee_config_authority.as_ref(),
276                withdraw_withheld_authority.as_ref(),
277                transfer_fee_basis_points,
278                maximum_fee,
279            ),
280            Self::InterestBearingConfig {
281                rate_authority,
282                rate,
283            } => interest_bearing_mint::instruction::initialize(
284                token_program_id,
285                mint,
286                rate_authority,
287                rate,
288            ),
289            Self::NonTransferable => {
290                instruction::initialize_non_transferable_mint(token_program_id, mint)
291            }
292            Self::PermanentDelegate { delegate } => {
293                instruction::initialize_permanent_delegate(token_program_id, mint, &delegate)
294            }
295            Self::TransferHook {
296                authority,
297                program_id,
298            } => transfer_hook::instruction::initialize(
299                token_program_id,
300                mint,
301                authority,
302                program_id,
303            ),
304            Self::MetadataPointer {
305                authority,
306                metadata_address,
307            } => metadata_pointer::instruction::initialize(
308                token_program_id,
309                mint,
310                authority,
311                metadata_address,
312            ),
313            Self::ConfidentialTransferFeeConfig {
314                authority,
315                withdraw_withheld_authority_elgamal_pubkey,
316            } => {
317                confidential_transfer_fee::instruction::initialize_confidential_transfer_fee_config(
318                    token_program_id,
319                    mint,
320                    authority,
321                    &withdraw_withheld_authority_elgamal_pubkey,
322                )
323            }
324            Self::GroupPointer {
325                authority,
326                group_address,
327            } => group_pointer::instruction::initialize(
328                token_program_id,
329                mint,
330                authority,
331                group_address,
332            ),
333            Self::GroupMemberPointer {
334                authority,
335                member_address,
336            } => group_member_pointer::instruction::initialize(
337                token_program_id,
338                mint,
339                authority,
340                member_address,
341            ),
342            Self::ScaledUiAmountConfig {
343                authority,
344                multiplier,
345            } => scaled_ui_amount::instruction::initialize(
346                token_program_id,
347                mint,
348                authority,
349                multiplier,
350            ),
351            Self::PausableConfig { authority } => {
352                pausable::instruction::initialize(token_program_id, mint, &authority)
353            }
354            Self::ConfidentialMintBurn {
355                supply_elgamal_pubkey,
356                decryptable_supply,
357            } => confidential_mint_burn::instruction::initialize_mint(
358                token_program_id,
359                mint,
360                &supply_elgamal_pubkey,
361                &decryptable_supply,
362            ),
363        }
364    }
365}
366
367pub type TokenResult<T> = Result<T, TokenError>;
368
369#[derive(Debug)]
370struct TokenMemo {
371    text: String,
372    signers: Vec<Pubkey>,
373}
374impl TokenMemo {
375    pub fn to_instruction(&self) -> Instruction {
376        spl_memo::build_memo(
377            self.text.as_bytes(),
378            &self.signers.iter().collect::<Vec<_>>(),
379        )
380    }
381}
382
383#[derive(Debug, Clone)]
384pub enum ComputeUnitLimit {
385    Default,
386    Simulated,
387    Static(u32),
388}
389
390pub struct ProofAccountWithCiphertext {
391    pub context_state_account: Pubkey,
392    pub ciphertext_lo: PodElGamalCiphertext,
393    pub ciphertext_hi: PodElGamalCiphertext,
394}
395
396pub struct Token<T> {
397    client: Arc<dyn ProgramClient<T>>,
398    pubkey: Pubkey, /* token mint */
399    decimals: Option<u8>,
400    payer: Arc<dyn Signer>,
401    program_id: Pubkey,
402    nonce_account: Option<Pubkey>,
403    nonce_authority: Option<Arc<dyn Signer>>,
404    nonce_blockhash: Option<Hash>,
405    memo: Arc<RwLock<Option<TokenMemo>>>,
406    transfer_hook_accounts: Option<Vec<AccountMeta>>,
407    compute_unit_price: Option<u64>,
408    compute_unit_limit: ComputeUnitLimit,
409}
410
411impl<T> fmt::Debug for Token<T> {
412    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
413        f.debug_struct("Token")
414            .field("pubkey", &self.pubkey)
415            .field("decimals", &self.decimals)
416            .field("payer", &self.payer.pubkey())
417            .field("program_id", &self.program_id)
418            .field("nonce_account", &self.nonce_account)
419            .field(
420                "nonce_authority",
421                &self.nonce_authority.as_ref().map(|s| s.pubkey()),
422            )
423            .field("nonce_blockhash", &self.nonce_blockhash)
424            .field("memo", &self.memo.read().unwrap())
425            .field("transfer_hook_accounts", &self.transfer_hook_accounts)
426            .field("compute_unit_price", &self.compute_unit_price)
427            .field("compute_unit_limit", &self.compute_unit_limit)
428            .finish()
429    }
430}
431
432fn native_mint(program_id: &Pubkey) -> Pubkey {
433    if program_id == &spl_token_2022::id() {
434        spl_token_2022::native_mint::id()
435    } else if program_id == &spl_token::id() {
436        spl_token::native_mint::id()
437    } else {
438        panic!("Unrecognized token program id: {}", program_id);
439    }
440}
441
442fn native_mint_decimals(program_id: &Pubkey) -> u8 {
443    if program_id == &spl_token_2022::id() {
444        spl_token_2022::native_mint::DECIMALS
445    } else if program_id == &spl_token::id() {
446        spl_token::native_mint::DECIMALS
447    } else {
448        panic!("Unrecognized token program id: {}", program_id);
449    }
450}
451
452impl<T> Token<T>
453where
454    T: SendTransaction + SimulateTransaction,
455{
456    pub fn new(
457        client: Arc<dyn ProgramClient<T>>,
458        program_id: &Pubkey,
459        address: &Pubkey,
460        decimals: Option<u8>,
461        payer: Arc<dyn Signer>,
462    ) -> Self {
463        Token {
464            client,
465            pubkey: *address,
466            decimals,
467            payer,
468            program_id: *program_id,
469            nonce_account: None,
470            nonce_authority: None,
471            nonce_blockhash: None,
472            memo: Arc::new(RwLock::new(None)),
473            transfer_hook_accounts: None,
474            compute_unit_price: None,
475            compute_unit_limit: ComputeUnitLimit::Default,
476        }
477    }
478
479    pub fn new_native(
480        client: Arc<dyn ProgramClient<T>>,
481        program_id: &Pubkey,
482        payer: Arc<dyn Signer>,
483    ) -> Self {
484        Self::new(
485            client,
486            program_id,
487            &native_mint(program_id),
488            Some(native_mint_decimals(program_id)),
489            payer,
490        )
491    }
492
493    pub fn is_native(&self) -> bool {
494        self.pubkey == native_mint(&self.program_id)
495    }
496
497    /// Get token address.
498    pub fn get_address(&self) -> &Pubkey {
499        &self.pubkey
500    }
501
502    pub fn with_payer(mut self, payer: Arc<dyn Signer>) -> Self {
503        self.payer = payer;
504        self
505    }
506
507    pub fn with_nonce(
508        mut self,
509        nonce_account: &Pubkey,
510        nonce_authority: Arc<dyn Signer>,
511        nonce_blockhash: &Hash,
512    ) -> Self {
513        self.nonce_account = Some(*nonce_account);
514        self.nonce_authority = Some(nonce_authority);
515        self.nonce_blockhash = Some(*nonce_blockhash);
516        self.transfer_hook_accounts = Some(vec![]);
517        self
518    }
519
520    pub fn with_transfer_hook_accounts(mut self, transfer_hook_accounts: Vec<AccountMeta>) -> Self {
521        self.transfer_hook_accounts = Some(transfer_hook_accounts);
522        self
523    }
524
525    pub fn with_compute_unit_price(mut self, compute_unit_price: u64) -> Self {
526        self.compute_unit_price = Some(compute_unit_price);
527        self
528    }
529
530    pub fn with_compute_unit_limit(mut self, compute_unit_limit: ComputeUnitLimit) -> Self {
531        self.compute_unit_limit = compute_unit_limit;
532        self
533    }
534
535    pub fn with_memo<M: AsRef<str>>(&self, memo: M, signers: Vec<Pubkey>) -> &Self {
536        let mut w_memo = self.memo.write().unwrap();
537        *w_memo = Some(TokenMemo {
538            text: memo.as_ref().to_string(),
539            signers,
540        });
541        self
542    }
543
544    pub async fn get_new_latest_blockhash(&self) -> TokenResult<Hash> {
545        let blockhash = self
546            .client
547            .get_latest_blockhash()
548            .await
549            .map_err(TokenError::Client)?;
550        let start = Instant::now();
551        let mut num_retries = 0;
552        while start.elapsed().as_secs() < 5 {
553            let new_blockhash = self
554                .client
555                .get_latest_blockhash()
556                .await
557                .map_err(TokenError::Client)?;
558            if new_blockhash != blockhash {
559                return Ok(new_blockhash);
560            }
561
562            time::sleep(Duration::from_millis(200)).await;
563            num_retries += 1;
564        }
565
566        Err(TokenError::Client(Box::new(io::Error::new(
567            io::ErrorKind::Other,
568            format!(
569                "Unable to get new blockhash after {}ms (retried {} times), stuck at {}",
570                start.elapsed().as_millis(),
571                num_retries,
572                blockhash
573            ),
574        ))))
575    }
576
577    fn get_multisig_signers<'a>(
578        &self,
579        authority: &Pubkey,
580        signing_pubkeys: &'a [Pubkey],
581    ) -> Vec<&'a Pubkey> {
582        if signing_pubkeys == [*authority] {
583            vec![]
584        } else {
585            signing_pubkeys.iter().collect::<Vec<_>>()
586        }
587    }
588
589    /// Helper function to add a compute unit limit instruction to a given set
590    /// of instructions
591    async fn add_compute_unit_limit_from_simulation(
592        &self,
593        instructions: &mut Vec<Instruction>,
594        blockhash: &Hash,
595    ) -> TokenResult<()> {
596        // add a max compute unit limit instruction for the simulation
597        const MAX_COMPUTE_UNIT_LIMIT: u32 = 1_400_000;
598        instructions.push(ComputeBudgetInstruction::set_compute_unit_limit(
599            MAX_COMPUTE_UNIT_LIMIT,
600        ));
601
602        let transaction = Transaction::new_unsigned(Message::new_with_blockhash(
603            instructions,
604            Some(&self.payer.pubkey()),
605            blockhash,
606        ));
607        let simulation_result = self
608            .client
609            .simulate_transaction(&transaction)
610            .await
611            .map_err(TokenError::Client)?;
612        let units_consumed = simulation_result
613            .get_compute_units_consumed()
614            .map_err(TokenError::Client)?;
615        // Overwrite the compute unit limit instruction with the actual units consumed
616        let compute_unit_limit =
617            u32::try_from(units_consumed).map_err(|x| TokenError::Client(x.into()))?;
618        instructions
619            .last_mut()
620            .expect("Compute budget instruction was added earlier")
621            .data = ComputeBudgetInstruction::set_compute_unit_limit(compute_unit_limit).data;
622        Ok(())
623    }
624
625    async fn construct_tx<S: Signers>(
626        &self,
627        token_instructions: &[Instruction],
628        signing_keypairs: &S,
629    ) -> TokenResult<Transaction> {
630        let mut instructions = vec![];
631        let payer_key = self.payer.pubkey();
632        let fee_payer = Some(&payer_key);
633
634        {
635            let mut w_memo = self.memo.write().unwrap();
636            if let Some(memo) = w_memo.take() {
637                let signing_pubkeys = signing_keypairs.pubkeys();
638                if !memo
639                    .signers
640                    .iter()
641                    .all(|signer| signing_pubkeys.contains(signer))
642                {
643                    return Err(TokenError::MissingMemoSigner);
644                }
645
646                instructions.push(memo.to_instruction());
647            }
648        }
649
650        instructions.extend_from_slice(token_instructions);
651
652        let blockhash = if let (Some(nonce_account), Some(nonce_authority), Some(nonce_blockhash)) = (
653            self.nonce_account,
654            &self.nonce_authority,
655            self.nonce_blockhash,
656        ) {
657            let nonce_instruction = system_instruction::advance_nonce_account(
658                &nonce_account,
659                &nonce_authority.pubkey(),
660            );
661            instructions.insert(0, nonce_instruction);
662            nonce_blockhash
663        } else {
664            self.client
665                .get_latest_blockhash()
666                .await
667                .map_err(TokenError::Client)?
668        };
669
670        if let Some(compute_unit_price) = self.compute_unit_price {
671            instructions.push(ComputeBudgetInstruction::set_compute_unit_price(
672                compute_unit_price,
673            ));
674        }
675
676        // The simulation to find out the compute unit usage must be run after
677        // all instructions have been added to the transaction, so be sure to
678        // keep this instruction as the last one before creating and sending the
679        // transaction.
680        match self.compute_unit_limit {
681            ComputeUnitLimit::Default => {}
682            ComputeUnitLimit::Simulated => {
683                self.add_compute_unit_limit_from_simulation(&mut instructions, &blockhash)
684                    .await?;
685            }
686            ComputeUnitLimit::Static(compute_unit_limit) => {
687                instructions.push(ComputeBudgetInstruction::set_compute_unit_limit(
688                    compute_unit_limit,
689                ));
690            }
691        }
692
693        let message = Message::new_with_blockhash(&instructions, fee_payer, &blockhash);
694        let mut transaction = Transaction::new_unsigned(message);
695        let signing_pubkeys = signing_keypairs.pubkeys();
696
697        if !signing_pubkeys.contains(&self.payer.pubkey()) {
698            transaction
699                .try_partial_sign(&vec![self.payer.clone()], blockhash)
700                .map_err(|error| TokenError::Client(error.into()))?;
701        }
702        if let Some(nonce_authority) = &self.nonce_authority {
703            let nonce_authority_pubkey = nonce_authority.pubkey();
704            if nonce_authority_pubkey != self.payer.pubkey()
705                && !signing_pubkeys.contains(&nonce_authority_pubkey)
706            {
707                transaction
708                    .try_partial_sign(&vec![nonce_authority.clone()], blockhash)
709                    .map_err(|error| TokenError::Client(error.into()))?;
710            }
711        }
712        transaction
713            .try_partial_sign(signing_keypairs, blockhash)
714            .map_err(|error| TokenError::Client(error.into()))?;
715
716        Ok(transaction)
717    }
718
719    pub async fn simulate_ixs<S: Signers>(
720        &self,
721        token_instructions: &[Instruction],
722        signing_keypairs: &S,
723    ) -> TokenResult<T::SimulationOutput> {
724        let transaction = self
725            .construct_tx(token_instructions, signing_keypairs)
726            .await?;
727
728        self.client
729            .simulate_transaction(&transaction)
730            .await
731            .map_err(TokenError::Client)
732    }
733
734    pub async fn process_ixs<S: Signers>(
735        &self,
736        token_instructions: &[Instruction],
737        signing_keypairs: &S,
738    ) -> TokenResult<T::Output> {
739        let transaction = self
740            .construct_tx(token_instructions, signing_keypairs)
741            .await?;
742
743        self.client
744            .send_transaction(&transaction)
745            .await
746            .map_err(TokenError::Client)
747    }
748
749    #[allow(clippy::too_many_arguments)]
750    pub async fn create_mint<'a, S: Signers>(
751        &self,
752        mint_authority: &'a Pubkey,
753        freeze_authority: Option<&'a Pubkey>,
754        extension_initialization_params: Vec<ExtensionInitializationParams>,
755        signing_keypairs: &S,
756    ) -> TokenResult<T::Output> {
757        let decimals = self.decimals.ok_or(TokenError::MissingDecimals)?;
758
759        let extension_types = extension_initialization_params
760            .iter()
761            .map(|e| e.extension())
762            .collect::<Vec<_>>();
763        let space = ExtensionType::try_calculate_account_len::<Mint>(&extension_types)?;
764
765        let mut instructions = vec![system_instruction::create_account(
766            &self.payer.pubkey(),
767            &self.pubkey,
768            self.client
769                .get_minimum_balance_for_rent_exemption(space)
770                .await
771                .map_err(TokenError::Client)?,
772            space as u64,
773            &self.program_id,
774        )];
775
776        for params in extension_initialization_params {
777            instructions.push(params.instruction(&self.program_id, &self.pubkey)?);
778        }
779
780        instructions.push(instruction::initialize_mint(
781            &self.program_id,
782            &self.pubkey,
783            mint_authority,
784            freeze_authority,
785            decimals,
786        )?);
787
788        self.process_ixs(&instructions, signing_keypairs).await
789    }
790
791    /// Create native mint
792    pub async fn create_native_mint(
793        client: Arc<dyn ProgramClient<T>>,
794        program_id: &Pubkey,
795        payer: Arc<dyn Signer>,
796    ) -> TokenResult<Self> {
797        let token = Self::new_native(client, program_id, payer);
798        token
799            .process_ixs::<[&dyn Signer; 0]>(
800                &[instruction::create_native_mint(
801                    program_id,
802                    &token.payer.pubkey(),
803                )?],
804                &[],
805            )
806            .await?;
807
808        Ok(token)
809    }
810
811    /// Create multisig
812    pub async fn create_multisig(
813        &self,
814        account: &dyn Signer,
815        multisig_members: &[&Pubkey],
816        minimum_signers: u8,
817    ) -> TokenResult<T::Output> {
818        let instructions = vec![
819            system_instruction::create_account(
820                &self.payer.pubkey(),
821                &account.pubkey(),
822                self.client
823                    .get_minimum_balance_for_rent_exemption(Multisig::LEN)
824                    .await
825                    .map_err(TokenError::Client)?,
826                Multisig::LEN as u64,
827                &self.program_id,
828            ),
829            instruction::initialize_multisig(
830                &self.program_id,
831                &account.pubkey(),
832                multisig_members,
833                minimum_signers,
834            )?,
835        ];
836
837        self.process_ixs(&instructions, &[account]).await
838    }
839
840    /// Get the address for the associated token account.
841    pub fn get_associated_token_address(&self, owner: &Pubkey) -> Pubkey {
842        get_associated_token_address_with_program_id(owner, &self.pubkey, &self.program_id)
843    }
844
845    /// Create and initialize the associated account.
846    pub async fn create_associated_token_account(&self, owner: &Pubkey) -> TokenResult<T::Output> {
847        self.process_ixs::<[&dyn Signer; 0]>(
848            &[create_associated_token_account(
849                &self.payer.pubkey(),
850                owner,
851                &self.pubkey,
852                &self.program_id,
853            )],
854            &[],
855        )
856        .await
857    }
858
859    /// Create and initialize a new token account.
860    pub async fn create_auxiliary_token_account(
861        &self,
862        account: &dyn Signer,
863        owner: &Pubkey,
864    ) -> TokenResult<T::Output> {
865        self.create_auxiliary_token_account_with_extension_space(account, owner, vec![])
866            .await
867    }
868
869    /// Create and initialize a new token account.
870    pub async fn create_auxiliary_token_account_with_extension_space(
871        &self,
872        account: &dyn Signer,
873        owner: &Pubkey,
874        extensions: Vec<ExtensionType>,
875    ) -> TokenResult<T::Output> {
876        let state = self.get_mint_info().await?;
877        let mint_extensions: Vec<ExtensionType> = state.get_extension_types()?;
878        let mut required_extensions =
879            ExtensionType::get_required_init_account_extensions(&mint_extensions);
880        for extension_type in extensions.into_iter() {
881            if !required_extensions.contains(&extension_type) {
882                required_extensions.push(extension_type);
883            }
884        }
885        let space = ExtensionType::try_calculate_account_len::<Account>(&required_extensions)?;
886        let mut instructions = vec![system_instruction::create_account(
887            &self.payer.pubkey(),
888            &account.pubkey(),
889            self.client
890                .get_minimum_balance_for_rent_exemption(space)
891                .await
892                .map_err(TokenError::Client)?,
893            space as u64,
894            &self.program_id,
895        )];
896
897        if required_extensions.contains(&ExtensionType::ImmutableOwner) {
898            instructions.push(instruction::initialize_immutable_owner(
899                &self.program_id,
900                &account.pubkey(),
901            )?)
902        }
903
904        instructions.push(instruction::initialize_account(
905            &self.program_id,
906            &account.pubkey(),
907            &self.pubkey,
908            owner,
909        )?);
910
911        self.process_ixs(&instructions, &[account]).await
912    }
913
914    /// Retrieve a raw account
915    pub async fn get_account(&self, account: Pubkey) -> TokenResult<BaseAccount> {
916        self.client
917            .get_account(account)
918            .await
919            .map_err(TokenError::Client)?
920            .ok_or(TokenError::AccountNotFound)
921    }
922
923    fn unpack_mint_info(
924        &self,
925        account: BaseAccount,
926    ) -> TokenResult<StateWithExtensionsOwned<Mint>> {
927        if account.owner != self.program_id {
928            return Err(TokenError::AccountInvalidOwner);
929        }
930
931        let mint_result =
932            StateWithExtensionsOwned::<Mint>::unpack(account.data).map_err(Into::into);
933
934        if let (Ok(mint), Some(decimals)) = (&mint_result, self.decimals) {
935            if decimals != mint.base.decimals {
936                return Err(TokenError::InvalidDecimals);
937            }
938        }
939
940        mint_result
941    }
942
943    /// Retrieve mint information.
944    pub async fn get_mint_info(&self) -> TokenResult<StateWithExtensionsOwned<Mint>> {
945        let account = self.get_account(self.pubkey).await?;
946        self.unpack_mint_info(account)
947    }
948
949    /// Retrieve account information.
950    pub async fn get_account_info(
951        &self,
952        account: &Pubkey,
953    ) -> TokenResult<StateWithExtensionsOwned<Account>> {
954        let account = self.get_account(*account).await?;
955        if account.owner != self.program_id {
956            return Err(TokenError::AccountInvalidOwner);
957        }
958        let account = StateWithExtensionsOwned::<Account>::unpack(account.data)?;
959        if account.base.mint != *self.get_address() {
960            return Err(TokenError::AccountInvalidMint);
961        }
962
963        Ok(account)
964    }
965
966    /// Retrieve the associated account or create one if not found.
967    pub async fn get_or_create_associated_account_info(
968        &self,
969        owner: &Pubkey,
970    ) -> TokenResult<StateWithExtensionsOwned<Account>> {
971        let account = self.get_associated_token_address(owner);
972        match self.get_account_info(&account).await {
973            Ok(account) => Ok(account),
974            // AccountInvalidOwner is possible if account already received some lamports.
975            Err(TokenError::AccountNotFound) | Err(TokenError::AccountInvalidOwner) => {
976                self.create_associated_token_account(owner).await?;
977                self.get_account_info(&account).await
978            }
979            Err(error) => Err(error),
980        }
981    }
982
983    /// Assign a new authority to the account.
984    pub async fn set_authority<S: Signers>(
985        &self,
986        account: &Pubkey,
987        authority: &Pubkey,
988        new_authority: Option<&Pubkey>,
989        authority_type: instruction::AuthorityType,
990        signing_keypairs: &S,
991    ) -> TokenResult<T::Output> {
992        let signing_pubkeys = signing_keypairs.pubkeys();
993        let multisig_signers = self.get_multisig_signers(authority, &signing_pubkeys);
994
995        self.process_ixs(
996            &[instruction::set_authority(
997                &self.program_id,
998                account,
999                new_authority,
1000                authority_type,
1001                authority,
1002                &multisig_signers,
1003            )?],
1004            signing_keypairs,
1005        )
1006        .await
1007    }
1008
1009    /// Mint new tokens
1010    pub async fn mint_to<S: Signers>(
1011        &self,
1012        destination: &Pubkey,
1013        authority: &Pubkey,
1014        amount: u64,
1015        signing_keypairs: &S,
1016    ) -> TokenResult<T::Output> {
1017        let signing_pubkeys = signing_keypairs.pubkeys();
1018        let multisig_signers = self.get_multisig_signers(authority, &signing_pubkeys);
1019
1020        let instructions = if let Some(decimals) = self.decimals {
1021            [instruction::mint_to_checked(
1022                &self.program_id,
1023                &self.pubkey,
1024                destination,
1025                authority,
1026                &multisig_signers,
1027                amount,
1028                decimals,
1029            )?]
1030        } else {
1031            [instruction::mint_to(
1032                &self.program_id,
1033                &self.pubkey,
1034                destination,
1035                authority,
1036                &multisig_signers,
1037                amount,
1038            )?]
1039        };
1040
1041        self.process_ixs(&instructions, signing_keypairs).await
1042    }
1043
1044    /// Transfer tokens to another account
1045    #[allow(clippy::too_many_arguments)]
1046    pub async fn transfer<S: Signers>(
1047        &self,
1048        source: &Pubkey,
1049        destination: &Pubkey,
1050        authority: &Pubkey,
1051        amount: u64,
1052        signing_keypairs: &S,
1053    ) -> TokenResult<T::Output> {
1054        let signing_pubkeys = signing_keypairs.pubkeys();
1055        let multisig_signers = self.get_multisig_signers(authority, &signing_pubkeys);
1056
1057        let fetch_account_data_fn = |address| {
1058            self.client
1059                .get_account(address)
1060                .map_ok(|opt| opt.map(|acc| acc.data))
1061        };
1062
1063        let instruction = if let Some(decimals) = self.decimals {
1064            if let Some(transfer_hook_accounts) = &self.transfer_hook_accounts {
1065                let mut instruction = instruction::transfer_checked(
1066                    &self.program_id,
1067                    source,
1068                    self.get_address(),
1069                    destination,
1070                    authority,
1071                    &multisig_signers,
1072                    amount,
1073                    decimals,
1074                )?;
1075                instruction.accounts.extend(transfer_hook_accounts.clone());
1076                instruction
1077            } else {
1078                offchain::create_transfer_checked_instruction_with_extra_metas(
1079                    &self.program_id,
1080                    source,
1081                    self.get_address(),
1082                    destination,
1083                    authority,
1084                    &multisig_signers,
1085                    amount,
1086                    decimals,
1087                    fetch_account_data_fn,
1088                )
1089                .await
1090                .map_err(|_| TokenError::AccountNotFound)?
1091            }
1092        } else {
1093            #[allow(deprecated)]
1094            instruction::transfer(
1095                &self.program_id,
1096                source,
1097                destination,
1098                authority,
1099                &multisig_signers,
1100                amount,
1101            )?
1102        };
1103
1104        self.process_ixs(&[instruction], signing_keypairs).await
1105    }
1106
1107    /// Transfer tokens to an associated account, creating it if it does not
1108    /// exist
1109    #[allow(clippy::too_many_arguments)]
1110    pub async fn create_recipient_associated_account_and_transfer<S: Signers>(
1111        &self,
1112        source: &Pubkey,
1113        destination: &Pubkey,
1114        destination_owner: &Pubkey,
1115        authority: &Pubkey,
1116        amount: u64,
1117        fee: Option<u64>,
1118        signing_keypairs: &S,
1119    ) -> TokenResult<T::Output> {
1120        let signing_pubkeys = signing_keypairs.pubkeys();
1121        let multisig_signers = self.get_multisig_signers(authority, &signing_pubkeys);
1122
1123        let fetch_account_data_fn = |address| {
1124            self.client
1125                .get_account(address)
1126                .map_ok(|opt| opt.map(|acc| acc.data))
1127        };
1128
1129        if *destination != self.get_associated_token_address(destination_owner) {
1130            return Err(TokenError::AccountInvalidAssociatedAddress);
1131        }
1132
1133        let mut instructions = vec![
1134            (create_associated_token_account_idempotent(
1135                &self.payer.pubkey(),
1136                destination_owner,
1137                &self.pubkey,
1138                &self.program_id,
1139            )),
1140        ];
1141
1142        if let Some(fee) = fee {
1143            let decimals = self.decimals.ok_or(TokenError::MissingDecimals)?;
1144            instructions.push(transfer_fee::instruction::transfer_checked_with_fee(
1145                &self.program_id,
1146                source,
1147                &self.pubkey,
1148                destination,
1149                authority,
1150                &multisig_signers,
1151                amount,
1152                decimals,
1153                fee,
1154            )?);
1155        } else if let Some(decimals) = self.decimals {
1156            instructions.push(
1157                if let Some(transfer_hook_accounts) = &self.transfer_hook_accounts {
1158                    let mut instruction = instruction::transfer_checked(
1159                        &self.program_id,
1160                        source,
1161                        self.get_address(),
1162                        destination,
1163                        authority,
1164                        &multisig_signers,
1165                        amount,
1166                        decimals,
1167                    )?;
1168                    instruction.accounts.extend(transfer_hook_accounts.clone());
1169                    instruction
1170                } else {
1171                    offchain::create_transfer_checked_instruction_with_extra_metas(
1172                        &self.program_id,
1173                        source,
1174                        self.get_address(),
1175                        destination,
1176                        authority,
1177                        &multisig_signers,
1178                        amount,
1179                        decimals,
1180                        fetch_account_data_fn,
1181                    )
1182                    .await
1183                    .map_err(|_| TokenError::AccountNotFound)?
1184                },
1185            );
1186        } else {
1187            #[allow(deprecated)]
1188            instructions.push(instruction::transfer(
1189                &self.program_id,
1190                source,
1191                destination,
1192                authority,
1193                &multisig_signers,
1194                amount,
1195            )?);
1196        }
1197
1198        self.process_ixs(&instructions, signing_keypairs).await
1199    }
1200
1201    /// Transfer tokens to another account, given an expected fee
1202    #[allow(clippy::too_many_arguments)]
1203    pub async fn transfer_with_fee<S: Signers>(
1204        &self,
1205        source: &Pubkey,
1206        destination: &Pubkey,
1207        authority: &Pubkey,
1208        amount: u64,
1209        fee: u64,
1210        signing_keypairs: &S,
1211    ) -> TokenResult<T::Output> {
1212        let signing_pubkeys = signing_keypairs.pubkeys();
1213        let multisig_signers = self.get_multisig_signers(authority, &signing_pubkeys);
1214        let decimals = self.decimals.ok_or(TokenError::MissingDecimals)?;
1215
1216        let fetch_account_data_fn = |address| {
1217            self.client
1218                .get_account(address)
1219                .map_ok(|opt| opt.map(|acc| acc.data))
1220        };
1221
1222        let instruction = if let Some(transfer_hook_accounts) = &self.transfer_hook_accounts {
1223            let mut instruction = transfer_fee::instruction::transfer_checked_with_fee(
1224                &self.program_id,
1225                source,
1226                self.get_address(),
1227                destination,
1228                authority,
1229                &multisig_signers,
1230                amount,
1231                decimals,
1232                fee,
1233            )?;
1234            instruction.accounts.extend(transfer_hook_accounts.clone());
1235            instruction
1236        } else {
1237            offchain::create_transfer_checked_with_fee_instruction_with_extra_metas(
1238                &self.program_id,
1239                source,
1240                self.get_address(),
1241                destination,
1242                authority,
1243                &multisig_signers,
1244                amount,
1245                decimals,
1246                fee,
1247                fetch_account_data_fn,
1248            )
1249            .await
1250            .map_err(|_| TokenError::AccountNotFound)?
1251        };
1252
1253        self.process_ixs(&[instruction], signing_keypairs).await
1254    }
1255
1256    /// Burn tokens from account
1257    pub async fn burn<S: Signers>(
1258        &self,
1259        source: &Pubkey,
1260        authority: &Pubkey,
1261        amount: u64,
1262        signing_keypairs: &S,
1263    ) -> TokenResult<T::Output> {
1264        let signing_pubkeys = signing_keypairs.pubkeys();
1265        let multisig_signers = self.get_multisig_signers(authority, &signing_pubkeys);
1266
1267        let instructions = if let Some(decimals) = self.decimals {
1268            [instruction::burn_checked(
1269                &self.program_id,
1270                source,
1271                &self.pubkey,
1272                authority,
1273                &multisig_signers,
1274                amount,
1275                decimals,
1276            )?]
1277        } else {
1278            [instruction::burn(
1279                &self.program_id,
1280                source,
1281                &self.pubkey,
1282                authority,
1283                &multisig_signers,
1284                amount,
1285            )?]
1286        };
1287
1288        self.process_ixs(&instructions, signing_keypairs).await
1289    }
1290
1291    /// Approve a delegate to spend tokens
1292    pub async fn approve<S: Signers>(
1293        &self,
1294        source: &Pubkey,
1295        delegate: &Pubkey,
1296        authority: &Pubkey,
1297        amount: u64,
1298        signing_keypairs: &S,
1299    ) -> TokenResult<T::Output> {
1300        let signing_pubkeys = signing_keypairs.pubkeys();
1301        let multisig_signers = self.get_multisig_signers(authority, &signing_pubkeys);
1302
1303        let instructions = if let Some(decimals) = self.decimals {
1304            [instruction::approve_checked(
1305                &self.program_id,
1306                source,
1307                &self.pubkey,
1308                delegate,
1309                authority,
1310                &multisig_signers,
1311                amount,
1312                decimals,
1313            )?]
1314        } else {
1315            [instruction::approve(
1316                &self.program_id,
1317                source,
1318                delegate,
1319                authority,
1320                &multisig_signers,
1321                amount,
1322            )?]
1323        };
1324
1325        self.process_ixs(&instructions, signing_keypairs).await
1326    }
1327
1328    /// Revoke a delegate
1329    pub async fn revoke<S: Signers>(
1330        &self,
1331        source: &Pubkey,
1332        authority: &Pubkey,
1333        signing_keypairs: &S,
1334    ) -> TokenResult<T::Output> {
1335        let signing_pubkeys = signing_keypairs.pubkeys();
1336        let multisig_signers = self.get_multisig_signers(authority, &signing_pubkeys);
1337
1338        self.process_ixs(
1339            &[instruction::revoke(
1340                &self.program_id,
1341                source,
1342                authority,
1343                &multisig_signers,
1344            )?],
1345            signing_keypairs,
1346        )
1347        .await
1348    }
1349
1350    /// Close an empty account and reclaim its lamports
1351    pub async fn close_account<S: Signers>(
1352        &self,
1353        account: &Pubkey,
1354        lamports_destination: &Pubkey,
1355        authority: &Pubkey,
1356        signing_keypairs: &S,
1357    ) -> TokenResult<T::Output> {
1358        let signing_pubkeys = signing_keypairs.pubkeys();
1359        let multisig_signers = self.get_multisig_signers(authority, &signing_pubkeys);
1360
1361        let mut instructions = vec![instruction::close_account(
1362            &self.program_id,
1363            account,
1364            lamports_destination,
1365            authority,
1366            &multisig_signers,
1367        )?];
1368
1369        if let Ok(Some(destination_account)) = self.client.get_account(*lamports_destination).await
1370        {
1371            if let Ok(destination_obj) =
1372                StateWithExtensionsOwned::<Account>::unpack(destination_account.data)
1373            {
1374                if destination_obj.base.is_native() {
1375                    instructions.push(instruction::sync_native(
1376                        &self.program_id,
1377                        lamports_destination,
1378                    )?);
1379                }
1380            }
1381        }
1382
1383        self.process_ixs(&instructions, signing_keypairs).await
1384    }
1385
1386    /// Close an account, reclaiming its lamports and tokens
1387    pub async fn empty_and_close_account<S: Signers>(
1388        &self,
1389        account_to_close: &Pubkey,
1390        lamports_destination: &Pubkey,
1391        tokens_destination: &Pubkey,
1392        authority: &Pubkey,
1393        signing_keypairs: &S,
1394    ) -> TokenResult<T::Output> {
1395        let signing_pubkeys = signing_keypairs.pubkeys();
1396        let multisig_signers = self.get_multisig_signers(authority, &signing_pubkeys);
1397
1398        // this implicitly validates that the mint on self is correct
1399        let account_state = self.get_account_info(account_to_close).await?;
1400
1401        let mut instructions = vec![];
1402
1403        if !self.is_native() && account_state.base.amount > 0 {
1404            // if a separate close authority is being used, it must be a delegate also
1405            if let Some(decimals) = self.decimals {
1406                instructions.push(instruction::transfer_checked(
1407                    &self.program_id,
1408                    account_to_close,
1409                    &self.pubkey,
1410                    tokens_destination,
1411                    authority,
1412                    &multisig_signers,
1413                    account_state.base.amount,
1414                    decimals,
1415                )?);
1416            } else {
1417                #[allow(deprecated)]
1418                instructions.push(instruction::transfer(
1419                    &self.program_id,
1420                    account_to_close,
1421                    tokens_destination,
1422                    authority,
1423                    &multisig_signers,
1424                    account_state.base.amount,
1425                )?);
1426            }
1427        }
1428
1429        instructions.push(instruction::close_account(
1430            &self.program_id,
1431            account_to_close,
1432            lamports_destination,
1433            authority,
1434            &multisig_signers,
1435        )?);
1436
1437        if let Ok(Some(destination_account)) = self.client.get_account(*lamports_destination).await
1438        {
1439            if let Ok(destination_obj) =
1440                StateWithExtensionsOwned::<Account>::unpack(destination_account.data)
1441            {
1442                if destination_obj.base.is_native() {
1443                    instructions.push(instruction::sync_native(
1444                        &self.program_id,
1445                        lamports_destination,
1446                    )?);
1447                }
1448            }
1449        }
1450
1451        self.process_ixs(&instructions, signing_keypairs).await
1452    }
1453
1454    /// Freeze a token account
1455    pub async fn freeze<S: Signers>(
1456        &self,
1457        account: &Pubkey,
1458        authority: &Pubkey,
1459        signing_keypairs: &S,
1460    ) -> TokenResult<T::Output> {
1461        let signing_pubkeys = signing_keypairs.pubkeys();
1462        let multisig_signers = self.get_multisig_signers(authority, &signing_pubkeys);
1463
1464        self.process_ixs(
1465            &[instruction::freeze_account(
1466                &self.program_id,
1467                account,
1468                &self.pubkey,
1469                authority,
1470                &multisig_signers,
1471            )?],
1472            signing_keypairs,
1473        )
1474        .await
1475    }
1476
1477    /// Thaw / unfreeze a token account
1478    pub async fn thaw<S: Signers>(
1479        &self,
1480        account: &Pubkey,
1481        authority: &Pubkey,
1482        signing_keypairs: &S,
1483    ) -> TokenResult<T::Output> {
1484        let signing_pubkeys = signing_keypairs.pubkeys();
1485        let multisig_signers = self.get_multisig_signers(authority, &signing_pubkeys);
1486
1487        self.process_ixs(
1488            &[instruction::thaw_account(
1489                &self.program_id,
1490                account,
1491                &self.pubkey,
1492                authority,
1493                &multisig_signers,
1494            )?],
1495            signing_keypairs,
1496        )
1497        .await
1498    }
1499
1500    /// Wrap lamports into native account
1501    pub async fn wrap<S: Signers>(
1502        &self,
1503        account: &Pubkey,
1504        owner: &Pubkey,
1505        lamports: u64,
1506        signing_keypairs: &S,
1507    ) -> TokenResult<T::Output> {
1508        // mutable owner for Tokenkeg, immutable otherwise
1509        let immutable_owner = self.program_id != spl_token::id();
1510        let instructions = self.wrap_ixs(account, owner, lamports, immutable_owner)?;
1511
1512        self.process_ixs(&instructions, signing_keypairs).await
1513    }
1514
1515    /// Wrap lamports into a native account that can always have its ownership
1516    /// changed
1517    pub async fn wrap_with_mutable_ownership<S: Signers>(
1518        &self,
1519        account: &Pubkey,
1520        owner: &Pubkey,
1521        lamports: u64,
1522        signing_keypairs: &S,
1523    ) -> TokenResult<T::Output> {
1524        let instructions = self.wrap_ixs(account, owner, lamports, false)?;
1525
1526        self.process_ixs(&instructions, signing_keypairs).await
1527    }
1528
1529    fn wrap_ixs(
1530        &self,
1531        account: &Pubkey,
1532        owner: &Pubkey,
1533        lamports: u64,
1534        immutable_owner: bool,
1535    ) -> TokenResult<Vec<Instruction>> {
1536        if !self.is_native() {
1537            return Err(TokenError::AccountInvalidMint);
1538        }
1539
1540        let mut instructions = vec![];
1541        if *account == self.get_associated_token_address(owner) {
1542            instructions.push(system_instruction::transfer(owner, account, lamports));
1543            instructions.push(create_associated_token_account(
1544                &self.payer.pubkey(),
1545                owner,
1546                &self.pubkey,
1547                &self.program_id,
1548            ));
1549        } else {
1550            let extensions = if immutable_owner {
1551                vec![ExtensionType::ImmutableOwner]
1552            } else {
1553                vec![]
1554            };
1555            let space = ExtensionType::try_calculate_account_len::<Account>(&extensions)?;
1556
1557            instructions.push(system_instruction::create_account(
1558                &self.payer.pubkey(),
1559                account,
1560                lamports,
1561                space as u64,
1562                &self.program_id,
1563            ));
1564
1565            if immutable_owner {
1566                instructions.push(instruction::initialize_immutable_owner(
1567                    &self.program_id,
1568                    account,
1569                )?)
1570            }
1571
1572            instructions.push(instruction::initialize_account(
1573                &self.program_id,
1574                account,
1575                &self.pubkey,
1576                owner,
1577            )?);
1578        };
1579
1580        Ok(instructions)
1581    }
1582
1583    /// Sync native account lamports
1584    pub async fn sync_native(&self, account: &Pubkey) -> TokenResult<T::Output> {
1585        self.process_ixs::<[&dyn Signer; 0]>(
1586            &[instruction::sync_native(&self.program_id, account)?],
1587            &[],
1588        )
1589        .await
1590    }
1591
1592    /// Set transfer fee
1593    pub async fn set_transfer_fee<S: Signers>(
1594        &self,
1595        authority: &Pubkey,
1596        transfer_fee_basis_points: u16,
1597        maximum_fee: u64,
1598        signing_keypairs: &S,
1599    ) -> TokenResult<T::Output> {
1600        let signing_pubkeys = signing_keypairs.pubkeys();
1601        let multisig_signers = self.get_multisig_signers(authority, &signing_pubkeys);
1602
1603        self.process_ixs(
1604            &[transfer_fee::instruction::set_transfer_fee(
1605                &self.program_id,
1606                &self.pubkey,
1607                authority,
1608                &multisig_signers,
1609                transfer_fee_basis_points,
1610                maximum_fee,
1611            )?],
1612            signing_keypairs,
1613        )
1614        .await
1615    }
1616
1617    /// Set default account state on mint
1618    pub async fn set_default_account_state<S: Signers>(
1619        &self,
1620        authority: &Pubkey,
1621        state: &AccountState,
1622        signing_keypairs: &S,
1623    ) -> TokenResult<T::Output> {
1624        let signing_pubkeys = signing_keypairs.pubkeys();
1625        let multisig_signers = self.get_multisig_signers(authority, &signing_pubkeys);
1626
1627        self.process_ixs(
1628            &[
1629                default_account_state::instruction::update_default_account_state(
1630                    &self.program_id,
1631                    &self.pubkey,
1632                    authority,
1633                    &multisig_signers,
1634                    state,
1635                )?,
1636            ],
1637            signing_keypairs,
1638        )
1639        .await
1640    }
1641
1642    /// Harvest withheld tokens to mint
1643    pub async fn harvest_withheld_tokens_to_mint(
1644        &self,
1645        sources: &[&Pubkey],
1646    ) -> TokenResult<T::Output> {
1647        self.process_ixs::<[&dyn Signer; 0]>(
1648            &[transfer_fee::instruction::harvest_withheld_tokens_to_mint(
1649                &self.program_id,
1650                &self.pubkey,
1651                sources,
1652            )?],
1653            &[],
1654        )
1655        .await
1656    }
1657
1658    /// Withdraw withheld tokens from mint
1659    pub async fn withdraw_withheld_tokens_from_mint<S: Signers>(
1660        &self,
1661        destination: &Pubkey,
1662        authority: &Pubkey,
1663        signing_keypairs: &S,
1664    ) -> TokenResult<T::Output> {
1665        let signing_pubkeys = signing_keypairs.pubkeys();
1666        let multisig_signers = self.get_multisig_signers(authority, &signing_pubkeys);
1667
1668        self.process_ixs(
1669            &[
1670                transfer_fee::instruction::withdraw_withheld_tokens_from_mint(
1671                    &self.program_id,
1672                    &self.pubkey,
1673                    destination,
1674                    authority,
1675                    &multisig_signers,
1676                )?,
1677            ],
1678            signing_keypairs,
1679        )
1680        .await
1681    }
1682
1683    /// Withdraw withheld tokens from accounts
1684    pub async fn withdraw_withheld_tokens_from_accounts<S: Signers>(
1685        &self,
1686        destination: &Pubkey,
1687        authority: &Pubkey,
1688        sources: &[&Pubkey],
1689        signing_keypairs: &S,
1690    ) -> TokenResult<T::Output> {
1691        let signing_pubkeys = signing_keypairs.pubkeys();
1692        let multisig_signers = self.get_multisig_signers(authority, &signing_pubkeys);
1693
1694        self.process_ixs(
1695            &[
1696                transfer_fee::instruction::withdraw_withheld_tokens_from_accounts(
1697                    &self.program_id,
1698                    &self.pubkey,
1699                    destination,
1700                    authority,
1701                    &multisig_signers,
1702                    sources,
1703                )?,
1704            ],
1705            signing_keypairs,
1706        )
1707        .await
1708    }
1709
1710    /// Reallocate a token account to be large enough for a set of
1711    /// `ExtensionType`s
1712    pub async fn reallocate<S: Signers>(
1713        &self,
1714        account: &Pubkey,
1715        authority: &Pubkey,
1716        extension_types: &[ExtensionType],
1717        signing_keypairs: &S,
1718    ) -> TokenResult<T::Output> {
1719        let signing_pubkeys = signing_keypairs.pubkeys();
1720        let multisig_signers = self.get_multisig_signers(authority, &signing_pubkeys);
1721
1722        self.process_ixs(
1723            &[instruction::reallocate(
1724                &self.program_id,
1725                account,
1726                &self.payer.pubkey(),
1727                authority,
1728                &multisig_signers,
1729                extension_types,
1730            )?],
1731            signing_keypairs,
1732        )
1733        .await
1734    }
1735
1736    /// Require memos on transfers into this account
1737    pub async fn enable_required_transfer_memos<S: Signers>(
1738        &self,
1739        account: &Pubkey,
1740        authority: &Pubkey,
1741        signing_keypairs: &S,
1742    ) -> TokenResult<T::Output> {
1743        let signing_pubkeys = signing_keypairs.pubkeys();
1744        let multisig_signers = self.get_multisig_signers(authority, &signing_pubkeys);
1745
1746        self.process_ixs(
1747            &[memo_transfer::instruction::enable_required_transfer_memos(
1748                &self.program_id,
1749                account,
1750                authority,
1751                &multisig_signers,
1752            )?],
1753            signing_keypairs,
1754        )
1755        .await
1756    }
1757
1758    /// Stop requiring memos on transfers into this account
1759    pub async fn disable_required_transfer_memos<S: Signers>(
1760        &self,
1761        account: &Pubkey,
1762        authority: &Pubkey,
1763        signing_keypairs: &S,
1764    ) -> TokenResult<T::Output> {
1765        let signing_pubkeys = signing_keypairs.pubkeys();
1766        let multisig_signers = self.get_multisig_signers(authority, &signing_pubkeys);
1767
1768        self.process_ixs(
1769            &[memo_transfer::instruction::disable_required_transfer_memos(
1770                &self.program_id,
1771                account,
1772                authority,
1773                &multisig_signers,
1774            )?],
1775            signing_keypairs,
1776        )
1777        .await
1778    }
1779
1780    /// Pause transferring, minting, and burning on the mint
1781    pub async fn pause<S: Signers>(
1782        &self,
1783        authority: &Pubkey,
1784        signing_keypairs: &S,
1785    ) -> TokenResult<T::Output> {
1786        let signing_pubkeys = signing_keypairs.pubkeys();
1787        let multisig_signers = self.get_multisig_signers(authority, &signing_pubkeys);
1788
1789        self.process_ixs(
1790            &[pausable::instruction::pause(
1791                &self.program_id,
1792                self.get_address(),
1793                authority,
1794                &multisig_signers,
1795            )?],
1796            signing_keypairs,
1797        )
1798        .await
1799    }
1800
1801    /// Resume transferring, minting, and burning on the mint
1802    pub async fn resume<S: Signers>(
1803        &self,
1804        authority: &Pubkey,
1805        signing_keypairs: &S,
1806    ) -> TokenResult<T::Output> {
1807        let signing_pubkeys = signing_keypairs.pubkeys();
1808        let multisig_signers = self.get_multisig_signers(authority, &signing_pubkeys);
1809
1810        self.process_ixs(
1811            &[pausable::instruction::resume(
1812                &self.program_id,
1813                self.get_address(),
1814                authority,
1815                &multisig_signers,
1816            )?],
1817            signing_keypairs,
1818        )
1819        .await
1820    }
1821
1822    /// Prevent unsafe usage of token account through CPI
1823    pub async fn enable_cpi_guard<S: Signers>(
1824        &self,
1825        account: &Pubkey,
1826        authority: &Pubkey,
1827        signing_keypairs: &S,
1828    ) -> TokenResult<T::Output> {
1829        let signing_pubkeys = signing_keypairs.pubkeys();
1830        let multisig_signers = self.get_multisig_signers(authority, &signing_pubkeys);
1831
1832        self.process_ixs(
1833            &[cpi_guard::instruction::enable_cpi_guard(
1834                &self.program_id,
1835                account,
1836                authority,
1837                &multisig_signers,
1838            )?],
1839            signing_keypairs,
1840        )
1841        .await
1842    }
1843
1844    /// Stop preventing unsafe usage of token account through CPI
1845    pub async fn disable_cpi_guard<S: Signers>(
1846        &self,
1847        account: &Pubkey,
1848        authority: &Pubkey,
1849        signing_keypairs: &S,
1850    ) -> TokenResult<T::Output> {
1851        let signing_pubkeys = signing_keypairs.pubkeys();
1852        let multisig_signers = self.get_multisig_signers(authority, &signing_pubkeys);
1853
1854        self.process_ixs(
1855            &[cpi_guard::instruction::disable_cpi_guard(
1856                &self.program_id,
1857                account,
1858                authority,
1859                &multisig_signers,
1860            )?],
1861            signing_keypairs,
1862        )
1863        .await
1864    }
1865
1866    /// Update interest rate
1867    pub async fn update_interest_rate<S: Signers>(
1868        &self,
1869        authority: &Pubkey,
1870        new_rate: i16,
1871        signing_keypairs: &S,
1872    ) -> TokenResult<T::Output> {
1873        let signing_pubkeys = signing_keypairs.pubkeys();
1874        let multisig_signers = self.get_multisig_signers(authority, &signing_pubkeys);
1875
1876        self.process_ixs(
1877            &[interest_bearing_mint::instruction::update_rate(
1878                &self.program_id,
1879                self.get_address(),
1880                authority,
1881                &multisig_signers,
1882                new_rate,
1883            )?],
1884            signing_keypairs,
1885        )
1886        .await
1887    }
1888
1889    /// Update multiplier
1890    pub async fn update_multiplier<S: Signers>(
1891        &self,
1892        authority: &Pubkey,
1893        new_multiplier: f64,
1894        new_multiplier_effective_timestamp: i64,
1895        signing_keypairs: &S,
1896    ) -> TokenResult<T::Output> {
1897        let signing_pubkeys = signing_keypairs.pubkeys();
1898        let multisig_signers = self.get_multisig_signers(authority, &signing_pubkeys);
1899
1900        self.process_ixs(
1901            &[scaled_ui_amount::instruction::update_multiplier(
1902                &self.program_id,
1903                self.get_address(),
1904                authority,
1905                &multisig_signers,
1906                new_multiplier,
1907                new_multiplier_effective_timestamp,
1908            )?],
1909            signing_keypairs,
1910        )
1911        .await
1912    }
1913
1914    /// Update transfer hook program id
1915    pub async fn update_transfer_hook_program_id<S: Signers>(
1916        &self,
1917        authority: &Pubkey,
1918        new_program_id: Option<Pubkey>,
1919        signing_keypairs: &S,
1920    ) -> TokenResult<T::Output> {
1921        let signing_pubkeys = signing_keypairs.pubkeys();
1922        let multisig_signers = self.get_multisig_signers(authority, &signing_pubkeys);
1923
1924        self.process_ixs(
1925            &[transfer_hook::instruction::update(
1926                &self.program_id,
1927                self.get_address(),
1928                authority,
1929                &multisig_signers,
1930                new_program_id,
1931            )?],
1932            signing_keypairs,
1933        )
1934        .await
1935    }
1936
1937    /// Update metadata pointer address
1938    pub async fn update_metadata_address<S: Signers>(
1939        &self,
1940        authority: &Pubkey,
1941        new_metadata_address: Option<Pubkey>,
1942        signing_keypairs: &S,
1943    ) -> TokenResult<T::Output> {
1944        let signing_pubkeys = signing_keypairs.pubkeys();
1945        let multisig_signers = self.get_multisig_signers(authority, &signing_pubkeys);
1946
1947        self.process_ixs(
1948            &[metadata_pointer::instruction::update(
1949                &self.program_id,
1950                self.get_address(),
1951                authority,
1952                &multisig_signers,
1953                new_metadata_address,
1954            )?],
1955            signing_keypairs,
1956        )
1957        .await
1958    }
1959
1960    /// Update group pointer address
1961    pub async fn update_group_address<S: Signers>(
1962        &self,
1963        authority: &Pubkey,
1964        new_group_address: Option<Pubkey>,
1965        signing_keypairs: &S,
1966    ) -> TokenResult<T::Output> {
1967        let signing_pubkeys = signing_keypairs.pubkeys();
1968        let multisig_signers = self.get_multisig_signers(authority, &signing_pubkeys);
1969
1970        self.process_ixs(
1971            &[group_pointer::instruction::update(
1972                &self.program_id,
1973                self.get_address(),
1974                authority,
1975                &multisig_signers,
1976                new_group_address,
1977            )?],
1978            signing_keypairs,
1979        )
1980        .await
1981    }
1982
1983    /// Update group member pointer address
1984    pub async fn update_group_member_address<S: Signers>(
1985        &self,
1986        authority: &Pubkey,
1987        new_member_address: Option<Pubkey>,
1988        signing_keypairs: &S,
1989    ) -> TokenResult<T::Output> {
1990        let signing_pubkeys = signing_keypairs.pubkeys();
1991        let multisig_signers = self.get_multisig_signers(authority, &signing_pubkeys);
1992
1993        self.process_ixs(
1994            &[group_member_pointer::instruction::update(
1995                &self.program_id,
1996                self.get_address(),
1997                authority,
1998                &multisig_signers,
1999                new_member_address,
2000            )?],
2001            signing_keypairs,
2002        )
2003        .await
2004    }
2005
2006    /// Update confidential transfer mint
2007    pub async fn confidential_transfer_update_mint<S: Signers>(
2008        &self,
2009        authority: &Pubkey,
2010        auto_approve_new_account: bool,
2011        auditor_elgamal_pubkey: Option<PodElGamalPubkey>,
2012        signing_keypairs: &S,
2013    ) -> TokenResult<T::Output> {
2014        let signing_pubkeys = signing_keypairs.pubkeys();
2015        let multisig_signers = self.get_multisig_signers(authority, &signing_pubkeys);
2016
2017        self.process_ixs(
2018            &[confidential_transfer::instruction::update_mint(
2019                &self.program_id,
2020                &self.pubkey,
2021                authority,
2022                &multisig_signers,
2023                auto_approve_new_account,
2024                auditor_elgamal_pubkey,
2025            )?],
2026            signing_keypairs,
2027        )
2028        .await
2029    }
2030
2031    /// Configures confidential transfers for a token account. If the maximum
2032    /// pending balance credit counter for the extension is not provided,
2033    /// then it is set to be a default value of `2^16`.
2034    #[allow(clippy::too_many_arguments)]
2035    pub async fn confidential_transfer_configure_token_account<S: Signers>(
2036        &self,
2037        account: &Pubkey,
2038        authority: &Pubkey,
2039        context_state_account: Option<&Pubkey>,
2040        maximum_pending_balance_credit_counter: Option<u64>,
2041        elgamal_keypair: &ElGamalKeypair,
2042        aes_key: &AeKey,
2043        signing_keypairs: &S,
2044    ) -> TokenResult<T::Output> {
2045        const DEFAULT_MAXIMUM_PENDING_BALANCE_CREDIT_COUNTER: u64 = 65536;
2046
2047        let signing_pubkeys = signing_keypairs.pubkeys();
2048        let multisig_signers = self.get_multisig_signers(authority, &signing_pubkeys);
2049
2050        let maximum_pending_balance_credit_counter = maximum_pending_balance_credit_counter
2051            .unwrap_or(DEFAULT_MAXIMUM_PENDING_BALANCE_CREDIT_COUNTER);
2052
2053        let proof_data = if context_state_account.is_some() {
2054            None
2055        } else {
2056            Some(
2057                confidential_transfer::instruction::PubkeyValidityProofData::new(elgamal_keypair)
2058                    .map_err(|_| TokenError::ProofGeneration)?,
2059            )
2060        };
2061
2062        // cannot panic as long as either `proof_data` or `context_state_account` is `Some(..)`,
2063        // which is guaranteed by the previous check
2064        let proof_location = Self::confidential_transfer_create_proof_location(
2065            proof_data.as_ref(),
2066            context_state_account,
2067            1,
2068        )
2069        .unwrap();
2070
2071        let decryptable_balance = aes_key.encrypt(0).into();
2072
2073        self.process_ixs(
2074            &confidential_transfer::instruction::configure_account(
2075                &self.program_id,
2076                account,
2077                &self.pubkey,
2078                &decryptable_balance,
2079                maximum_pending_balance_credit_counter,
2080                authority,
2081                &multisig_signers,
2082                proof_location,
2083            )?,
2084            signing_keypairs,
2085        )
2086        .await
2087    }
2088
2089    /// Configures confidential transfers for a token account using an ElGamal
2090    /// registry account
2091    pub async fn confidential_transfer_configure_token_account_with_registry(
2092        &self,
2093        account: &Pubkey,
2094        elgamal_registry_account: &Pubkey,
2095        payer: Option<&Pubkey>,
2096    ) -> TokenResult<T::Output> {
2097        self.process_ixs::<[&dyn Signer; 0]>(
2098            &[
2099                confidential_transfer::instruction::configure_account_with_registry(
2100                    &self.program_id,
2101                    account,
2102                    &self.pubkey,
2103                    elgamal_registry_account,
2104                    payer,
2105                )?,
2106            ],
2107            &[],
2108        )
2109        .await
2110    }
2111
2112    /// Approves a token account for confidential transfers
2113    pub async fn confidential_transfer_approve_account<S: Signers>(
2114        &self,
2115        account: &Pubkey,
2116        authority: &Pubkey,
2117        signing_keypairs: &S,
2118    ) -> TokenResult<T::Output> {
2119        let signing_pubkeys = signing_keypairs.pubkeys();
2120        let multisig_signers = self.get_multisig_signers(authority, &signing_pubkeys);
2121
2122        self.process_ixs(
2123            &[confidential_transfer::instruction::approve_account(
2124                &self.program_id,
2125                account,
2126                &self.pubkey,
2127                authority,
2128                &multisig_signers,
2129            )?],
2130            signing_keypairs,
2131        )
2132        .await
2133    }
2134
2135    /// Prepare a token account with the confidential transfer extension for
2136    /// closing
2137    pub async fn confidential_transfer_empty_account<S: Signers>(
2138        &self,
2139        account: &Pubkey,
2140        authority: &Pubkey,
2141        context_state_account: Option<&Pubkey>,
2142        account_info: Option<EmptyAccountAccountInfo>,
2143        elgamal_keypair: &ElGamalKeypair,
2144        signing_keypairs: &S,
2145    ) -> TokenResult<T::Output> {
2146        let signing_pubkeys = signing_keypairs.pubkeys();
2147        let multisig_signers = self.get_multisig_signers(authority, &signing_pubkeys);
2148
2149        let account_info = if let Some(account_info) = account_info {
2150            account_info
2151        } else {
2152            let account = self.get_account_info(account).await?;
2153            let confidential_transfer_account =
2154                account.get_extension::<ConfidentialTransferAccount>()?;
2155            EmptyAccountAccountInfo::new(confidential_transfer_account)
2156        };
2157
2158        let proof_data = if context_state_account.is_some() {
2159            None
2160        } else {
2161            Some(
2162                account_info
2163                    .generate_proof_data(elgamal_keypair)
2164                    .map_err(|_| TokenError::ProofGeneration)?,
2165            )
2166        };
2167
2168        // cannot panic as long as either `proof_data` or `context_state_account` is `Some(..)`,
2169        // which is guaranteed by the previous check
2170        let proof_location = Self::confidential_transfer_create_proof_location(
2171            proof_data.as_ref(),
2172            context_state_account,
2173            1,
2174        )
2175        .unwrap();
2176
2177        self.process_ixs(
2178            &confidential_transfer::instruction::empty_account(
2179                &self.program_id,
2180                account,
2181                authority,
2182                &multisig_signers,
2183                proof_location,
2184            )?,
2185            signing_keypairs,
2186        )
2187        .await
2188    }
2189
2190    /// Deposit SPL Tokens into the pending balance of a confidential token
2191    /// account
2192    pub async fn confidential_transfer_deposit<S: Signers>(
2193        &self,
2194        account: &Pubkey,
2195        authority: &Pubkey,
2196        amount: u64,
2197        decimals: u8,
2198        signing_keypairs: &S,
2199    ) -> TokenResult<T::Output> {
2200        let signing_pubkeys = signing_keypairs.pubkeys();
2201        let multisig_signers = self.get_multisig_signers(authority, &signing_pubkeys);
2202
2203        self.process_ixs(
2204            &[confidential_transfer::instruction::deposit(
2205                &self.program_id,
2206                account,
2207                &self.pubkey,
2208                amount,
2209                decimals,
2210                authority,
2211                &multisig_signers,
2212            )?],
2213            signing_keypairs,
2214        )
2215        .await
2216    }
2217
2218    /// Withdraw SPL Tokens from the available balance of a confidential token
2219    /// account
2220    #[allow(clippy::too_many_arguments)]
2221    pub async fn confidential_transfer_withdraw<S: Signers>(
2222        &self,
2223        account: &Pubkey,
2224        authority: &Pubkey,
2225        equality_proof_account: Option<&Pubkey>,
2226        range_proof_account: Option<&Pubkey>,
2227        withdraw_amount: u64,
2228        decimals: u8,
2229        account_info: Option<WithdrawAccountInfo>,
2230        elgamal_keypair: &ElGamalKeypair,
2231        aes_key: &AeKey,
2232        signing_keypairs: &S,
2233    ) -> TokenResult<T::Output> {
2234        let signing_pubkeys = signing_keypairs.pubkeys();
2235        let multisig_signers = self.get_multisig_signers(authority, &signing_pubkeys);
2236
2237        let account_info = if let Some(account_info) = account_info {
2238            account_info
2239        } else {
2240            let account = self.get_account_info(account).await?;
2241            let confidential_transfer_account =
2242                account.get_extension::<ConfidentialTransferAccount>()?;
2243            WithdrawAccountInfo::new(confidential_transfer_account)
2244        };
2245
2246        let (equality_proof_data, range_proof_data) =
2247            if equality_proof_account.is_some() && range_proof_account.is_some() {
2248                (None, None)
2249            } else {
2250                let WithdrawProofData {
2251                    equality_proof_data,
2252                    range_proof_data,
2253                } = account_info
2254                    .generate_proof_data(withdraw_amount, elgamal_keypair, aes_key)
2255                    .map_err(|_| TokenError::ProofGeneration)?;
2256
2257                // if proof accounts are none, then proof data must be included as instruction
2258                // data
2259                let equality_proof_data = equality_proof_account
2260                    .is_none()
2261                    .then_some(equality_proof_data);
2262                let range_proof_data = range_proof_account.is_none().then_some(range_proof_data);
2263
2264                (equality_proof_data, range_proof_data)
2265            };
2266
2267        // cannot panic as long as either `proof_data` or `proof_account` is `Some(..)`,
2268        // which is guaranteed by the previous check
2269        let equality_proof_location = Self::confidential_transfer_create_proof_location(
2270            equality_proof_data.as_ref(),
2271            equality_proof_account,
2272            1,
2273        )
2274        .unwrap();
2275
2276        let range_proof_location = Self::confidential_transfer_create_proof_location(
2277            range_proof_data.as_ref(),
2278            range_proof_account,
2279            2,
2280        )
2281        .unwrap();
2282
2283        let new_decryptable_available_balance = account_info
2284            .new_decryptable_available_balance(withdraw_amount, aes_key)
2285            .map_err(|_| TokenError::AccountDecryption)?
2286            .into();
2287
2288        self.process_ixs(
2289            &confidential_transfer::instruction::withdraw(
2290                &self.program_id,
2291                account,
2292                &self.pubkey,
2293                withdraw_amount,
2294                decimals,
2295                &new_decryptable_available_balance,
2296                authority,
2297                &multisig_signers,
2298                equality_proof_location,
2299                range_proof_location,
2300            )?,
2301            signing_keypairs,
2302        )
2303        .await
2304    }
2305
2306    /// Transfer tokens confidentially
2307    #[allow(clippy::too_many_arguments)]
2308    pub async fn confidential_transfer_transfer<S: Signers>(
2309        &self,
2310        source_account: &Pubkey,
2311        destination_account: &Pubkey,
2312        source_authority: &Pubkey,
2313        equality_proof_account: Option<&Pubkey>,
2314        ciphertext_validity_proof_account_with_ciphertext: Option<&ProofAccountWithCiphertext>,
2315        range_proof_account: Option<&Pubkey>,
2316        transfer_amount: u64,
2317        account_info: Option<TransferAccountInfo>,
2318        source_elgamal_keypair: &ElGamalKeypair,
2319        source_aes_key: &AeKey,
2320        destination_elgamal_pubkey: &ElGamalPubkey,
2321        auditor_elgamal_pubkey: Option<&ElGamalPubkey>,
2322        signing_keypairs: &S,
2323    ) -> TokenResult<T::Output> {
2324        let signing_pubkeys = signing_keypairs.pubkeys();
2325        let multisig_signers = self.get_multisig_signers(source_authority, &signing_pubkeys);
2326
2327        let account_info = if let Some(account_info) = account_info {
2328            account_info
2329        } else {
2330            let account = self.get_account_info(source_account).await?;
2331            let confidential_transfer_account =
2332                account.get_extension::<ConfidentialTransferAccount>()?;
2333            TransferAccountInfo::new(confidential_transfer_account)
2334        };
2335
2336        let (equality_proof_data, ciphertext_validity_proof_data_with_ciphertext, range_proof_data) =
2337            if equality_proof_account.is_some()
2338                && ciphertext_validity_proof_account_with_ciphertext.is_some()
2339                && range_proof_account.is_some()
2340            {
2341                (None, None, None)
2342            } else {
2343                let TransferProofData {
2344                    equality_proof_data,
2345                    ciphertext_validity_proof_data_with_ciphertext,
2346                    range_proof_data,
2347                } = account_info
2348                    .generate_split_transfer_proof_data(
2349                        transfer_amount,
2350                        source_elgamal_keypair,
2351                        source_aes_key,
2352                        destination_elgamal_pubkey,
2353                        auditor_elgamal_pubkey,
2354                    )
2355                    .map_err(|_| TokenError::ProofGeneration)?;
2356
2357                // if proof accounts are none, then proof data must be included as instruction
2358                // data
2359                let equality_proof_data = equality_proof_account
2360                    .is_none()
2361                    .then_some(equality_proof_data);
2362                let ciphertext_validity_proof_data_with_ciphertext =
2363                    ciphertext_validity_proof_account_with_ciphertext
2364                        .is_none()
2365                        .then_some(ciphertext_validity_proof_data_with_ciphertext);
2366                let range_proof_data = range_proof_account.is_none().then_some(range_proof_data);
2367
2368                (
2369                    equality_proof_data,
2370                    ciphertext_validity_proof_data_with_ciphertext,
2371                    range_proof_data,
2372                )
2373            };
2374
2375        let (transfer_amount_auditor_ciphertext_lo, transfer_amount_auditor_ciphertext_hi) =
2376            if let Some(proof_data_with_ciphertext) = ciphertext_validity_proof_data_with_ciphertext
2377            {
2378                (
2379                    proof_data_with_ciphertext.ciphertext_lo,
2380                    proof_data_with_ciphertext.ciphertext_hi,
2381                )
2382            } else {
2383                // unwrap is safe as long as either `proof_data_with_ciphertext`,
2384                // `proof_account_with_ciphertext` is `Some(..)`, which is guaranteed by the
2385                // previous check
2386                (
2387                    ciphertext_validity_proof_account_with_ciphertext
2388                        .unwrap()
2389                        .ciphertext_lo,
2390                    ciphertext_validity_proof_account_with_ciphertext
2391                        .unwrap()
2392                        .ciphertext_hi,
2393                )
2394            };
2395
2396        // cannot panic as long as either `proof_data` or `proof_account` is `Some(..)`,
2397        // which is guaranteed by the previous check
2398        let equality_proof_location = Self::confidential_transfer_create_proof_location(
2399            equality_proof_data.as_ref(),
2400            equality_proof_account,
2401            1,
2402        )
2403        .unwrap();
2404        let ciphertext_validity_proof_data =
2405            ciphertext_validity_proof_data_with_ciphertext.map(|data| data.proof_data);
2406        let ciphertext_validity_proof_location = Self::confidential_transfer_create_proof_location(
2407            ciphertext_validity_proof_data.as_ref(),
2408            ciphertext_validity_proof_account_with_ciphertext
2409                .map(|account| &account.context_state_account),
2410            2,
2411        )
2412        .unwrap();
2413        let range_proof_location = Self::confidential_transfer_create_proof_location(
2414            range_proof_data.as_ref(),
2415            range_proof_account,
2416            3,
2417        )
2418        .unwrap();
2419
2420        let new_decryptable_available_balance = account_info
2421            .new_decryptable_available_balance(transfer_amount, source_aes_key)
2422            .map_err(|_| TokenError::AccountDecryption)?
2423            .into();
2424
2425        let mut instructions = confidential_transfer::instruction::transfer(
2426            &self.program_id,
2427            source_account,
2428            self.get_address(),
2429            destination_account,
2430            &new_decryptable_available_balance,
2431            &transfer_amount_auditor_ciphertext_lo,
2432            &transfer_amount_auditor_ciphertext_hi,
2433            source_authority,
2434            &multisig_signers,
2435            equality_proof_location,
2436            ciphertext_validity_proof_location,
2437            range_proof_location,
2438        )?;
2439        offchain::add_extra_account_metas(
2440            &mut instructions[0],
2441            source_account,
2442            self.get_address(),
2443            destination_account,
2444            source_authority,
2445            u64::MAX,
2446            |address| {
2447                self.client
2448                    .get_account(address)
2449                    .map_ok(|opt| opt.map(|acc| acc.data))
2450            },
2451        )
2452        .await
2453        .map_err(|_| TokenError::AccountNotFound)?;
2454        self.process_ixs(&instructions, signing_keypairs).await
2455    }
2456
2457    /// Create a record account containing zero-knowledge proof needed for a
2458    /// confidential transfer.
2459    pub async fn confidential_transfer_create_record_account<
2460        S1: Signer,
2461        S2: Signer,
2462        ZK: Pod + ZkProofData<U>,
2463        U: Pod,
2464    >(
2465        &self,
2466        record_account: &Pubkey,
2467        record_authority: &Pubkey,
2468        proof_data: &ZK,
2469        record_account_signer: &S1,
2470        record_authority_signer: &S2,
2471    ) -> TokenResult<Vec<T::Output>> {
2472        let proof_data = bytes_of(proof_data);
2473        let space = proof_data
2474            .len()
2475            .saturating_add(RecordData::WRITABLE_START_INDEX);
2476        let rent = self
2477            .client
2478            .get_minimum_balance_for_rent_exemption(space)
2479            .await
2480            .map_err(TokenError::Client)?;
2481
2482        // A closure that constructs a vector of instructions needed to create and write
2483        // to record accounts. The closure is defined as a convenience function
2484        // to be fed into the function `calculate_record_max_chunk_size`.
2485        let create_record_instructions = |first_instruction: bool, bytes: &[u8], offset: u64| {
2486            let mut ixs = vec![];
2487            if first_instruction {
2488                ixs.push(system_instruction::create_account(
2489                    &self.payer.pubkey(),
2490                    record_account,
2491                    rent,
2492                    space as u64,
2493                    &spl_record::id(),
2494                ));
2495                ixs.push(spl_record::instruction::initialize(
2496                    record_account,
2497                    record_authority,
2498                ));
2499            }
2500            ixs.push(spl_record::instruction::write(
2501                record_account,
2502                record_authority,
2503                offset,
2504                bytes,
2505            ));
2506            ixs
2507        };
2508        let first_chunk_size = calculate_record_max_chunk_size(create_record_instructions, true);
2509        let (first_chunk, rest) = if space <= first_chunk_size {
2510            (proof_data, &[] as &[u8])
2511        } else {
2512            proof_data.split_at(first_chunk_size)
2513        };
2514
2515        let first_ixs = create_record_instructions(true, first_chunk, 0);
2516        let first_ixs_signers: [&dyn Signer; 2] = [record_account_signer, record_authority_signer];
2517        self.process_ixs(&first_ixs, &first_ixs_signers).await?;
2518
2519        let subsequent_chunk_size =
2520            calculate_record_max_chunk_size(create_record_instructions, false);
2521        let mut record_offset = first_chunk_size;
2522        let mut ixs_batch = vec![];
2523        for chunk in rest.chunks(subsequent_chunk_size) {
2524            ixs_batch.push(create_record_instructions(
2525                false,
2526                chunk,
2527                record_offset as u64,
2528            ));
2529            record_offset = record_offset.saturating_add(chunk.len());
2530        }
2531
2532        let futures = ixs_batch
2533            .into_iter()
2534            .map(|ixs| async move { self.process_ixs(&ixs, &[record_authority_signer]).await })
2535            .collect::<Vec<_>>();
2536
2537        join_all(futures).await.into_iter().collect()
2538    }
2539
2540    /// Close a record account.
2541    pub async fn confidential_transfer_close_record_account<S: Signers>(
2542        &self,
2543        record_account: &Pubkey,
2544        lamport_destination_account: &Pubkey,
2545        record_account_authority: &Pubkey,
2546        signing_keypairs: &S,
2547    ) -> TokenResult<T::Output> {
2548        self.process_ixs(
2549            &[spl_record::instruction::close_account(
2550                record_account,
2551                record_account_authority,
2552                lamport_destination_account,
2553            )],
2554            signing_keypairs,
2555        )
2556        .await
2557    }
2558
2559    /// Create a context state account containing zero-knowledge proof needed
2560    /// for a confidential transfer instruction.
2561    pub async fn confidential_transfer_create_context_state_account<
2562        S: Signers,
2563        ZK: Pod + ZkProofData<U>,
2564        U: Pod,
2565    >(
2566        &self,
2567        context_state_account: &Pubkey,
2568        context_state_authority: &Pubkey,
2569        proof_data: &ZK,
2570        split_account_creation_and_proof_verification: bool,
2571        signing_keypairs: &S,
2572    ) -> TokenResult<T::Output> {
2573        let instruction_type = zk_proof_type_to_instruction(ZK::PROOF_TYPE)?;
2574        let space = size_of::<ProofContextState<U>>();
2575        let rent = self
2576            .client
2577            .get_minimum_balance_for_rent_exemption(space)
2578            .await
2579            .map_err(TokenError::Client)?;
2580
2581        let context_state_info = ContextStateInfo {
2582            context_state_account,
2583            context_state_authority,
2584        };
2585
2586        // Some proof instructions are right at the transaction size limit, but in the
2587        // future it might be able to support the transfer too
2588        if split_account_creation_and_proof_verification {
2589            self.process_ixs(
2590                &[system_instruction::create_account(
2591                    &self.payer.pubkey(),
2592                    context_state_account,
2593                    rent,
2594                    space as u64,
2595                    &zk_elgamal_proof_program::id(),
2596                )],
2597                signing_keypairs,
2598            )
2599            .await?;
2600
2601            let blockhash = self
2602                .client
2603                .get_latest_blockhash()
2604                .await
2605                .map_err(TokenError::Client)?;
2606
2607            let transaction = Transaction::new_signed_with_payer(
2608                &[instruction_type.encode_verify_proof(Some(context_state_info), proof_data)],
2609                Some(&self.payer.pubkey()),
2610                &[self.payer.as_ref()],
2611                blockhash,
2612            );
2613
2614            self.client
2615                .send_transaction(&transaction)
2616                .await
2617                .map_err(TokenError::Client)
2618        } else {
2619            self.process_ixs(
2620                &[
2621                    system_instruction::create_account(
2622                        &self.payer.pubkey(),
2623                        context_state_account,
2624                        rent,
2625                        space as u64,
2626                        &zk_elgamal_proof_program::id(),
2627                    ),
2628                    instruction_type.encode_verify_proof(Some(context_state_info), proof_data),
2629                ],
2630                signing_keypairs,
2631            )
2632            .await
2633        }
2634    }
2635
2636    /// Create a context state account from another account containing
2637    /// zero-knowledge proof needed for a confidential transfer instruction.
2638    pub async fn confidential_transfer_create_context_state_account_from_record<
2639        S: Signers,
2640        ZK: Pod + ZkProofData<U>,
2641        U: Pod,
2642    >(
2643        &self,
2644        context_state_account: &Pubkey,
2645        context_state_authority: &Pubkey,
2646        record_account: &Pubkey,
2647        signing_keypairs: &S,
2648    ) -> TokenResult<T::Output> {
2649        const RECORD_ACCOUNT_PROOF_OFFSET: u32 = 33;
2650
2651        let instruction_type = zk_proof_type_to_instruction(ZK::PROOF_TYPE)?;
2652        let space = size_of::<ProofContextState<U>>();
2653        let rent = self
2654            .client
2655            .get_minimum_balance_for_rent_exemption(space)
2656            .await
2657            .map_err(TokenError::Client)?;
2658
2659        let context_state_info = ContextStateInfo {
2660            context_state_account,
2661            context_state_authority,
2662        };
2663
2664        // Some proof instructions are right at the transaction size limit, but in the
2665        // future it might be able to support the transfer too
2666        self.process_ixs(
2667            &[
2668                system_instruction::create_account(
2669                    &self.payer.pubkey(),
2670                    context_state_account,
2671                    rent,
2672                    space as u64,
2673                    &zk_elgamal_proof_program::id(),
2674                ),
2675                instruction_type.encode_verify_proof_from_account(
2676                    Some(context_state_info),
2677                    record_account,
2678                    RECORD_ACCOUNT_PROOF_OFFSET,
2679                ),
2680            ],
2681            signing_keypairs,
2682        )
2683        .await
2684    }
2685
2686    /// Close a ZK Token proof program context state
2687    pub async fn confidential_transfer_close_context_state_account<S: Signers>(
2688        &self,
2689        context_state_account: &Pubkey,
2690        lamport_destination_account: &Pubkey,
2691        context_state_authority: &Pubkey,
2692        signing_keypairs: &S,
2693    ) -> TokenResult<T::Output> {
2694        let context_state_info = ContextStateInfo {
2695            context_state_account,
2696            context_state_authority,
2697        };
2698
2699        self.process_ixs(
2700            &[close_context_state(
2701                context_state_info,
2702                lamport_destination_account,
2703            )],
2704            signing_keypairs,
2705        )
2706        .await
2707    }
2708
2709    /// Transfer tokens confidentially with fee
2710    #[allow(clippy::too_many_arguments)]
2711    pub async fn confidential_transfer_transfer_with_fee<S: Signers>(
2712        &self,
2713        source_account: &Pubkey,
2714        destination_account: &Pubkey,
2715        source_authority: &Pubkey,
2716        equality_proof_account: Option<&Pubkey>,
2717        transfer_amount_ciphertext_validity_proof_account_with_ciphertext: Option<
2718            &ProofAccountWithCiphertext,
2719        >,
2720        percentage_with_cap_proof_account: Option<&Pubkey>,
2721        fee_ciphertext_validity_proof_account: Option<&Pubkey>,
2722        range_proof_account: Option<&Pubkey>,
2723        transfer_amount: u64,
2724        account_info: Option<TransferAccountInfo>,
2725        source_elgamal_keypair: &ElGamalKeypair,
2726        source_aes_key: &AeKey,
2727        destination_elgamal_pubkey: &ElGamalPubkey,
2728        auditor_elgamal_pubkey: Option<&ElGamalPubkey>,
2729        withdraw_withheld_authority_elgamal_pubkey: &ElGamalPubkey,
2730        fee_rate_basis_points: u16,
2731        maximum_fee: u64,
2732        signing_keypairs: &S,
2733    ) -> TokenResult<T::Output> {
2734        let signing_pubkeys = signing_keypairs.pubkeys();
2735        let multisig_signers = self.get_multisig_signers(source_authority, &signing_pubkeys);
2736
2737        let account_info = if let Some(account_info) = account_info {
2738            account_info
2739        } else {
2740            let account = self.get_account_info(source_account).await?;
2741            let confidential_transfer_account =
2742                account.get_extension::<ConfidentialTransferAccount>()?;
2743            TransferAccountInfo::new(confidential_transfer_account)
2744        };
2745
2746        let (
2747            equality_proof_data,
2748            transfer_amount_ciphertext_validity_proof_data_with_ciphertext,
2749            percentage_with_cap_proof_data,
2750            fee_ciphertext_validity_proof_data,
2751            range_proof_data,
2752        ) = if equality_proof_account.is_some()
2753            && transfer_amount_ciphertext_validity_proof_account_with_ciphertext.is_some()
2754            && percentage_with_cap_proof_account.is_some()
2755            && fee_ciphertext_validity_proof_account.is_some()
2756            && range_proof_account.is_some()
2757        {
2758            // if all proofs come from accounts, then skip proof generation
2759            (None, None, None, None, None)
2760        } else {
2761            let TransferWithFeeProofData {
2762                equality_proof_data,
2763                transfer_amount_ciphertext_validity_proof_data_with_ciphertext,
2764                percentage_with_cap_proof_data,
2765                fee_ciphertext_validity_proof_data,
2766                range_proof_data,
2767            } = account_info
2768                .generate_split_transfer_with_fee_proof_data(
2769                    transfer_amount,
2770                    source_elgamal_keypair,
2771                    source_aes_key,
2772                    destination_elgamal_pubkey,
2773                    auditor_elgamal_pubkey,
2774                    withdraw_withheld_authority_elgamal_pubkey,
2775                    fee_rate_basis_points,
2776                    maximum_fee,
2777                )
2778                .map_err(|_| TokenError::ProofGeneration)?;
2779
2780            let equality_proof_data = equality_proof_account
2781                .is_none()
2782                .then_some(equality_proof_data);
2783            let transfer_amount_ciphertext_validity_proof_data_with_ciphertext =
2784                transfer_amount_ciphertext_validity_proof_account_with_ciphertext
2785                    .is_none()
2786                    .then_some(transfer_amount_ciphertext_validity_proof_data_with_ciphertext);
2787            let percentage_with_cap_proof_data = percentage_with_cap_proof_account
2788                .is_none()
2789                .then_some(percentage_with_cap_proof_data);
2790            let fee_ciphertext_validity_proof_data = fee_ciphertext_validity_proof_account
2791                .is_none()
2792                .then_some(fee_ciphertext_validity_proof_data);
2793            let range_proof_data = range_proof_account.is_none().then_some(range_proof_data);
2794
2795            (
2796                equality_proof_data,
2797                transfer_amount_ciphertext_validity_proof_data_with_ciphertext,
2798                percentage_with_cap_proof_data,
2799                fee_ciphertext_validity_proof_data,
2800                range_proof_data,
2801            )
2802        };
2803
2804        let (transfer_amount_auditor_ciphertext_lo, transfer_amount_auditor_ciphertext_hi) =
2805            if let Some(proof_data_with_ciphertext) =
2806                transfer_amount_ciphertext_validity_proof_data_with_ciphertext
2807            {
2808                (
2809                    proof_data_with_ciphertext.ciphertext_lo,
2810                    proof_data_with_ciphertext.ciphertext_hi,
2811                )
2812            } else {
2813                // unwrap is safe as long as either `proof_data_with_ciphertext`,
2814                // `proof_account_with_ciphertext` is `Some(..)`, which is guaranteed by the
2815                // previous check
2816                (
2817                    transfer_amount_ciphertext_validity_proof_account_with_ciphertext
2818                        .unwrap()
2819                        .ciphertext_lo,
2820                    transfer_amount_ciphertext_validity_proof_account_with_ciphertext
2821                        .unwrap()
2822                        .ciphertext_hi,
2823                )
2824            };
2825
2826        // cannot panic as long as either `proof_data` or `proof_account` is `Some(..)`,
2827        // which is guaranteed by the previous check
2828        let equality_proof_location = Self::confidential_transfer_create_proof_location(
2829            equality_proof_data.as_ref(),
2830            equality_proof_account,
2831            1,
2832        )
2833        .unwrap();
2834        let transfer_amount_ciphertext_validity_proof_data =
2835            transfer_amount_ciphertext_validity_proof_data_with_ciphertext
2836                .map(|data| data.proof_data);
2837        let transfer_amount_ciphertext_validity_proof_location =
2838            Self::confidential_transfer_create_proof_location(
2839                transfer_amount_ciphertext_validity_proof_data.as_ref(),
2840                transfer_amount_ciphertext_validity_proof_account_with_ciphertext
2841                    .map(|account| &account.context_state_account),
2842                2,
2843            )
2844            .unwrap();
2845        let fee_sigma_proof_location = Self::confidential_transfer_create_proof_location(
2846            percentage_with_cap_proof_data.as_ref(),
2847            percentage_with_cap_proof_account,
2848            3,
2849        )
2850        .unwrap();
2851        let fee_ciphertext_validity_proof_location =
2852            Self::confidential_transfer_create_proof_location(
2853                fee_ciphertext_validity_proof_data.as_ref(),
2854                fee_ciphertext_validity_proof_account,
2855                4,
2856            )
2857            .unwrap();
2858        let range_proof_location = Self::confidential_transfer_create_proof_location(
2859            range_proof_data.as_ref(),
2860            range_proof_account,
2861            5,
2862        )
2863        .unwrap();
2864
2865        let new_decryptable_available_balance = account_info
2866            .new_decryptable_available_balance(transfer_amount, source_aes_key)
2867            .map_err(|_| TokenError::AccountDecryption)?
2868            .into();
2869
2870        let mut instructions = confidential_transfer::instruction::transfer_with_fee(
2871            &self.program_id,
2872            source_account,
2873            self.get_address(),
2874            destination_account,
2875            &new_decryptable_available_balance,
2876            &transfer_amount_auditor_ciphertext_lo,
2877            &transfer_amount_auditor_ciphertext_hi,
2878            source_authority,
2879            &multisig_signers,
2880            equality_proof_location,
2881            transfer_amount_ciphertext_validity_proof_location,
2882            fee_sigma_proof_location,
2883            fee_ciphertext_validity_proof_location,
2884            range_proof_location,
2885        )?;
2886        offchain::add_extra_account_metas(
2887            &mut instructions[0],
2888            source_account,
2889            self.get_address(),
2890            destination_account,
2891            source_authority,
2892            u64::MAX,
2893            |address| {
2894                self.client
2895                    .get_account(address)
2896                    .map_ok(|opt| opt.map(|acc| acc.data))
2897            },
2898        )
2899        .await
2900        .map_err(|_| TokenError::AccountNotFound)?;
2901        self.process_ixs(&instructions, signing_keypairs).await
2902    }
2903
2904    /// Applies the confidential transfer pending balance to the available
2905    /// balance
2906    pub async fn confidential_transfer_apply_pending_balance<S: Signers>(
2907        &self,
2908        account: &Pubkey,
2909        authority: &Pubkey,
2910        account_info: Option<ApplyPendingBalanceAccountInfo>,
2911        elgamal_secret_key: &ElGamalSecretKey,
2912        aes_key: &AeKey,
2913        signing_keypairs: &S,
2914    ) -> TokenResult<T::Output> {
2915        let signing_pubkeys = signing_keypairs.pubkeys();
2916        let multisig_signers = self.get_multisig_signers(authority, &signing_pubkeys);
2917
2918        let account_info = if let Some(account_info) = account_info {
2919            account_info
2920        } else {
2921            let account = self.get_account_info(account).await?;
2922            let confidential_transfer_account =
2923                account.get_extension::<ConfidentialTransferAccount>()?;
2924            ApplyPendingBalanceAccountInfo::new(confidential_transfer_account)
2925        };
2926
2927        let expected_pending_balance_credit_counter = account_info.pending_balance_credit_counter();
2928        let new_decryptable_available_balance = account_info
2929            .new_decryptable_available_balance(elgamal_secret_key, aes_key)
2930            .map_err(|_| TokenError::AccountDecryption)?
2931            .into();
2932
2933        self.process_ixs(
2934            &[confidential_transfer::instruction::apply_pending_balance(
2935                &self.program_id,
2936                account,
2937                expected_pending_balance_credit_counter,
2938                &new_decryptable_available_balance,
2939                authority,
2940                &multisig_signers,
2941            )?],
2942            signing_keypairs,
2943        )
2944        .await
2945    }
2946
2947    /// Enable confidential transfer `Deposit` and `Transfer` instructions for a
2948    /// token account
2949    pub async fn confidential_transfer_enable_confidential_credits<S: Signers>(
2950        &self,
2951        account: &Pubkey,
2952        authority: &Pubkey,
2953        signing_keypairs: &S,
2954    ) -> TokenResult<T::Output> {
2955        let signing_pubkeys = signing_keypairs.pubkeys();
2956        let multisig_signers = self.get_multisig_signers(authority, &signing_pubkeys);
2957
2958        self.process_ixs(
2959            &[
2960                confidential_transfer::instruction::enable_confidential_credits(
2961                    &self.program_id,
2962                    account,
2963                    authority,
2964                    &multisig_signers,
2965                )?,
2966            ],
2967            signing_keypairs,
2968        )
2969        .await
2970    }
2971
2972    /// Disable confidential transfer `Deposit` and `Transfer` instructions for
2973    /// a token account
2974    pub async fn confidential_transfer_disable_confidential_credits<S: Signers>(
2975        &self,
2976        account: &Pubkey,
2977        authority: &Pubkey,
2978        signing_keypairs: &S,
2979    ) -> TokenResult<T::Output> {
2980        let signing_pubkeys = signing_keypairs.pubkeys();
2981        let multisig_signers = self.get_multisig_signers(authority, &signing_pubkeys);
2982
2983        self.process_ixs(
2984            &[
2985                confidential_transfer::instruction::disable_confidential_credits(
2986                    &self.program_id,
2987                    account,
2988                    authority,
2989                    &multisig_signers,
2990                )?,
2991            ],
2992            signing_keypairs,
2993        )
2994        .await
2995    }
2996
2997    /// Enable a confidential extension token account to receive
2998    /// non-confidential payments
2999    pub async fn confidential_transfer_enable_non_confidential_credits<S: Signers>(
3000        &self,
3001        account: &Pubkey,
3002        authority: &Pubkey,
3003        signing_keypairs: &S,
3004    ) -> TokenResult<T::Output> {
3005        let signing_pubkeys = signing_keypairs.pubkeys();
3006        let multisig_signers = self.get_multisig_signers(authority, &signing_pubkeys);
3007
3008        self.process_ixs(
3009            &[
3010                confidential_transfer::instruction::enable_non_confidential_credits(
3011                    &self.program_id,
3012                    account,
3013                    authority,
3014                    &multisig_signers,
3015                )?,
3016            ],
3017            signing_keypairs,
3018        )
3019        .await
3020    }
3021
3022    /// Disable non-confidential payments for a confidential extension token
3023    /// account
3024    pub async fn confidential_transfer_disable_non_confidential_credits<S: Signers>(
3025        &self,
3026        account: &Pubkey,
3027        authority: &Pubkey,
3028        signing_keypairs: &S,
3029    ) -> TokenResult<T::Output> {
3030        let signing_pubkeys = signing_keypairs.pubkeys();
3031        let multisig_signers = self.get_multisig_signers(authority, &signing_pubkeys);
3032
3033        self.process_ixs(
3034            &[
3035                confidential_transfer::instruction::disable_non_confidential_credits(
3036                    &self.program_id,
3037                    account,
3038                    authority,
3039                    &multisig_signers,
3040                )?,
3041            ],
3042            signing_keypairs,
3043        )
3044        .await
3045    }
3046
3047    /// Withdraw withheld confidential tokens from mint
3048    #[allow(clippy::too_many_arguments)]
3049    pub async fn confidential_transfer_withdraw_withheld_tokens_from_mint<S: Signers>(
3050        &self,
3051        destination_account: &Pubkey,
3052        withdraw_withheld_authority: &Pubkey,
3053        context_state_account: Option<&Pubkey>,
3054        withheld_tokens_info: Option<WithheldTokensInfo>,
3055        withdraw_withheld_authority_elgamal_keypair: &ElGamalKeypair,
3056        destination_elgamal_pubkey: &ElGamalPubkey,
3057        new_decryptable_available_balance: &DecryptableBalance,
3058        signing_keypairs: &S,
3059    ) -> TokenResult<T::Output> {
3060        let signing_pubkeys = signing_keypairs.pubkeys();
3061        let multisig_signers =
3062            self.get_multisig_signers(withdraw_withheld_authority, &signing_pubkeys);
3063
3064        let account_info = if let Some(account_info) = withheld_tokens_info {
3065            account_info
3066        } else {
3067            let mint_info = self.get_mint_info().await?;
3068            let confidential_transfer_fee_config =
3069                mint_info.get_extension::<ConfidentialTransferFeeConfig>()?;
3070            WithheldTokensInfo::new(&confidential_transfer_fee_config.withheld_amount)
3071        };
3072
3073        let proof_data = if context_state_account.is_some() {
3074            None
3075        } else {
3076            Some(
3077                account_info
3078                    .generate_proof_data(
3079                        withdraw_withheld_authority_elgamal_keypair,
3080                        destination_elgamal_pubkey,
3081                    )
3082                    .map_err(|_| TokenError::ProofGeneration)?,
3083            )
3084        };
3085
3086        // cannot panic as long as either `proof_data` or `context_state_account` is `Some(..)`,
3087        // which is guaranteed by the previous check
3088        let proof_location = Self::confidential_transfer_create_proof_location(
3089            proof_data.as_ref(),
3090            context_state_account,
3091            1,
3092        )
3093        .unwrap();
3094
3095        self.process_ixs(
3096            &confidential_transfer_fee::instruction::withdraw_withheld_tokens_from_mint(
3097                &self.program_id,
3098                &self.pubkey,
3099                destination_account,
3100                new_decryptable_available_balance,
3101                withdraw_withheld_authority,
3102                &multisig_signers,
3103                proof_location,
3104            )?,
3105            signing_keypairs,
3106        )
3107        .await
3108    }
3109
3110    /// Withdraw withheld confidential tokens from accounts
3111    #[allow(clippy::too_many_arguments)]
3112    pub async fn confidential_transfer_withdraw_withheld_tokens_from_accounts<S: Signers>(
3113        &self,
3114        destination_account: &Pubkey,
3115        withdraw_withheld_authority: &Pubkey,
3116        context_state_account: Option<&Pubkey>,
3117        withheld_tokens_info: Option<WithheldTokensInfo>,
3118        withdraw_withheld_authority_elgamal_keypair: &ElGamalKeypair,
3119        destination_elgamal_pubkey: &ElGamalPubkey,
3120        new_decryptable_available_balance: &DecryptableBalance,
3121        sources: &[&Pubkey],
3122        signing_keypairs: &S,
3123    ) -> TokenResult<T::Output> {
3124        let signing_pubkeys = signing_keypairs.pubkeys();
3125        let multisig_signers =
3126            self.get_multisig_signers(withdraw_withheld_authority, &signing_pubkeys);
3127
3128        let account_info = if let Some(account_info) = withheld_tokens_info {
3129            account_info
3130        } else {
3131            let futures = sources.iter().map(|source| self.get_account_info(source));
3132            let sources_extensions = join_all(futures).await;
3133
3134            let mut aggregate_withheld_amount = ElGamalCiphertext::default();
3135            for source_extension in sources_extensions {
3136                let withheld_amount: ElGamalCiphertext = source_extension?
3137                    .get_extension::<ConfidentialTransferFeeAmount>()?
3138                    .withheld_amount
3139                    .try_into()
3140                    .map_err(|_| TokenError::AccountDecryption)?;
3141                aggregate_withheld_amount = aggregate_withheld_amount + withheld_amount;
3142            }
3143
3144            WithheldTokensInfo::new(&aggregate_withheld_amount.into())
3145        };
3146
3147        let proof_data = if context_state_account.is_some() {
3148            None
3149        } else {
3150            Some(
3151                account_info
3152                    .generate_proof_data(
3153                        withdraw_withheld_authority_elgamal_keypair,
3154                        destination_elgamal_pubkey,
3155                    )
3156                    .map_err(|_| TokenError::ProofGeneration)?,
3157            )
3158        };
3159
3160        // cannot panic as long as either `proof_data` or `proof_account` is `Some(..)`,
3161        // which is guaranteed by the previous check
3162        let proof_location = Self::confidential_transfer_create_proof_location(
3163            proof_data.as_ref(),
3164            context_state_account,
3165            1,
3166        )
3167        .unwrap();
3168
3169        self.process_ixs(
3170            &confidential_transfer_fee::instruction::withdraw_withheld_tokens_from_accounts(
3171                &self.program_id,
3172                &self.pubkey,
3173                destination_account,
3174                new_decryptable_available_balance,
3175                withdraw_withheld_authority,
3176                &multisig_signers,
3177                sources,
3178                proof_location,
3179            )?,
3180            signing_keypairs,
3181        )
3182        .await
3183    }
3184
3185    /// Harvest withheld confidential tokens to mint
3186    pub async fn confidential_transfer_harvest_withheld_tokens_to_mint(
3187        &self,
3188        sources: &[&Pubkey],
3189    ) -> TokenResult<T::Output> {
3190        self.process_ixs::<[&dyn Signer; 0]>(
3191            &[
3192                confidential_transfer_fee::instruction::harvest_withheld_tokens_to_mint(
3193                    &self.program_id,
3194                    &self.pubkey,
3195                    sources,
3196                )?,
3197            ],
3198            &[],
3199        )
3200        .await
3201    }
3202
3203    /// Enable harvest of confidential fees to mint
3204    pub async fn confidential_transfer_enable_harvest_to_mint<S: Signers>(
3205        &self,
3206        withdraw_withheld_authority: &Pubkey,
3207        signing_keypairs: &S,
3208    ) -> TokenResult<T::Output> {
3209        let signing_pubkeys = signing_keypairs.pubkeys();
3210        let multisig_signers =
3211            self.get_multisig_signers(withdraw_withheld_authority, &signing_pubkeys);
3212
3213        self.process_ixs(
3214            &[
3215                confidential_transfer_fee::instruction::enable_harvest_to_mint(
3216                    &self.program_id,
3217                    &self.pubkey,
3218                    withdraw_withheld_authority,
3219                    &multisig_signers,
3220                )?,
3221            ],
3222            signing_keypairs,
3223        )
3224        .await
3225    }
3226
3227    /// Disable harvest of confidential fees to mint
3228    pub async fn confidential_transfer_disable_harvest_to_mint<S: Signers>(
3229        &self,
3230        withdraw_withheld_authority: &Pubkey,
3231        signing_keypairs: &S,
3232    ) -> TokenResult<T::Output> {
3233        let signing_pubkeys = signing_keypairs.pubkeys();
3234        let multisig_signers =
3235            self.get_multisig_signers(withdraw_withheld_authority, &signing_pubkeys);
3236
3237        self.process_ixs(
3238            &[
3239                confidential_transfer_fee::instruction::disable_harvest_to_mint(
3240                    &self.program_id,
3241                    &self.pubkey,
3242                    withdraw_withheld_authority,
3243                    &multisig_signers,
3244                )?,
3245            ],
3246            signing_keypairs,
3247        )
3248        .await
3249    }
3250
3251    /// Rotate supply ElGamal public key in a confidential mint
3252    #[allow(clippy::too_many_arguments)]
3253    pub async fn confidential_transfer_rotate_supply_elgamal_pubkey<S: Signers>(
3254        &self,
3255        authority: &Pubkey,
3256        current_supply_elgamal_keypair: &ElGamalKeypair,
3257        new_supply_elgamal_pubkey: &ElGamalPubkey,
3258        aes_key: &AeKey,
3259        context_state_account: Option<&Pubkey>,
3260        account_info: Option<SupplyAccountInfo>,
3261        signing_keypairs: &S,
3262    ) -> TokenResult<T::Output> {
3263        let signing_pubkeys = signing_keypairs.pubkeys();
3264        let multisig_signers = self.get_multisig_signers(authority, &signing_pubkeys);
3265
3266        let account_info = if let Some(account_info) = account_info {
3267            account_info
3268        } else {
3269            let account = self.get_mint_info().await?;
3270            let confidential_supply_account = account.get_extension::<ConfidentialMintBurn>()?;
3271            SupplyAccountInfo::new(confidential_supply_account)
3272        };
3273
3274        let proof_data = if context_state_account.is_some() {
3275            None
3276        } else {
3277            Some(
3278                account_info
3279                    .generate_rotate_supply_elgamal_pubkey_proof(
3280                        current_supply_elgamal_keypair,
3281                        new_supply_elgamal_pubkey,
3282                        aes_key,
3283                    )
3284                    .map_err(|_| TokenError::ProofGeneration)?,
3285            )
3286        };
3287
3288        // cannot panic as long as either `proof_data` or `proof_account` is `Some(..)`,
3289        // which is guaranteed by the previous check
3290        let proof_location = Self::confidential_transfer_create_proof_location(
3291            proof_data.as_ref(),
3292            context_state_account,
3293            1,
3294        )
3295        .unwrap();
3296
3297        let new_supply_elgamal_pubkey = (*new_supply_elgamal_pubkey).into();
3298        self.process_ixs(
3299            &confidential_mint_burn::instruction::rotate_supply_elgamal_pubkey(
3300                &self.program_id,
3301                &self.pubkey,
3302                authority,
3303                &multisig_signers,
3304                &new_supply_elgamal_pubkey,
3305                proof_location,
3306            )?,
3307            signing_keypairs,
3308        )
3309        .await
3310    }
3311
3312    /// Update decryptable supply in a confidential mint
3313    pub async fn confidential_transfer_update_decrypt_supply<S: Signers>(
3314        &self,
3315        authority: &Pubkey,
3316        new_decryptable_supply: &DecryptableBalance,
3317        signing_keypairs: &S,
3318    ) -> TokenResult<T::Output> {
3319        let signing_pubkeys = signing_keypairs.pubkeys();
3320        let multisig_signers = self.get_multisig_signers(authority, &signing_pubkeys);
3321
3322        self.process_ixs(
3323            &[
3324                confidential_mint_burn::instruction::update_decryptable_supply(
3325                    &self.program_id,
3326                    &self.pubkey,
3327                    authority,
3328                    &multisig_signers,
3329                    new_decryptable_supply,
3330                )?,
3331            ],
3332            signing_keypairs,
3333        )
3334        .await
3335    }
3336
3337    /// Confidentially mint tokens
3338    #[allow(clippy::too_many_arguments)]
3339    pub async fn confidential_transfer_mint<S: Signers>(
3340        &self,
3341        authority: &Pubkey,
3342        destination_account: &Pubkey,
3343        equality_proof_account: Option<&Pubkey>,
3344        ciphertext_validity_proof_account_with_ciphertext: Option<&ProofAccountWithCiphertext>,
3345        range_proof_account: Option<&Pubkey>,
3346        mint_amount: u64,
3347        supply_elgamal_keypair: &ElGamalKeypair,
3348        destination_elgamal_pubkey: &ElGamalPubkey,
3349        auditor_elgamal_pubkey: Option<&ElGamalPubkey>,
3350        aes_key: &AeKey,
3351        account_info: Option<SupplyAccountInfo>,
3352        signing_keypairs: &S,
3353    ) -> TokenResult<T::Output> {
3354        let signing_pubkeys = signing_keypairs.pubkeys();
3355        let multisig_signers = self.get_multisig_signers(authority, &signing_pubkeys);
3356
3357        let account_info = if let Some(account_info) = account_info {
3358            account_info
3359        } else {
3360            let account = self.get_mint_info().await?;
3361            let confidential_supply_account = account.get_extension::<ConfidentialMintBurn>()?;
3362            SupplyAccountInfo::new(confidential_supply_account)
3363        };
3364
3365        let (equality_proof_data, ciphertext_validity_proof_data_with_ciphertext, range_proof_data) =
3366            if equality_proof_account.is_some()
3367                && ciphertext_validity_proof_account_with_ciphertext.is_some()
3368                && range_proof_account.is_some()
3369            {
3370                // if all proofs come from accounts, then skip proof generation
3371                (None, None, None)
3372            } else {
3373                let MintProofData {
3374                    equality_proof_data,
3375                    ciphertext_validity_proof_data_with_ciphertext,
3376                    range_proof_data,
3377                } = account_info
3378                    .generate_split_mint_proof_data(
3379                        mint_amount,
3380                        supply_elgamal_keypair,
3381                        aes_key,
3382                        destination_elgamal_pubkey,
3383                        auditor_elgamal_pubkey,
3384                    )
3385                    .map_err(|_| TokenError::ProofGeneration)?;
3386
3387                let equality_proof_data = equality_proof_account
3388                    .is_none()
3389                    .then_some(equality_proof_data);
3390                let ciphertext_validity_proof_data_with_ciphertext =
3391                    ciphertext_validity_proof_account_with_ciphertext
3392                        .is_none()
3393                        .then_some(ciphertext_validity_proof_data_with_ciphertext);
3394                let range_proof_data = range_proof_account.is_none().then_some(range_proof_data);
3395
3396                (
3397                    equality_proof_data,
3398                    ciphertext_validity_proof_data_with_ciphertext,
3399                    range_proof_data,
3400                )
3401            };
3402
3403        // cannot panic as long as either `proof_data` or `proof_account` is `Some(..)`,
3404        // which is guaranteed by the previous check
3405        let equality_proof_location = Self::confidential_transfer_create_proof_location(
3406            equality_proof_data.as_ref(),
3407            equality_proof_account,
3408            1,
3409        )
3410        .unwrap();
3411        let ciphertext_validity_proof_data =
3412            ciphertext_validity_proof_data_with_ciphertext.map(|data| data.proof_data);
3413        let ciphertext_validity_proof_location = Self::confidential_transfer_create_proof_location(
3414            ciphertext_validity_proof_data.as_ref(),
3415            ciphertext_validity_proof_account_with_ciphertext
3416                .map(|account| &account.context_state_account),
3417            2,
3418        )
3419        .unwrap();
3420        let range_proof_location = Self::confidential_transfer_create_proof_location(
3421            range_proof_data.as_ref(),
3422            range_proof_account,
3423            3,
3424        )
3425        .unwrap();
3426
3427        let (mint_amount_auditor_ciphertext_lo, mint_amount_auditor_ciphertext_hi) = if let Some(
3428            proof_data_with_ciphertext,
3429        ) =
3430            ciphertext_validity_proof_data_with_ciphertext
3431        {
3432            (
3433                proof_data_with_ciphertext.ciphertext_lo,
3434                proof_data_with_ciphertext.ciphertext_hi,
3435            )
3436        } else {
3437            // unwrap is safe as long as either `proof_data_with_ciphertext`,
3438            // `proof_account_with_ciphertext` is `Some(..)`, which is guaranteed by the
3439            // previous check
3440            (
3441                ciphertext_validity_proof_account_with_ciphertext
3442                    .unwrap()
3443                    .ciphertext_lo,
3444                ciphertext_validity_proof_account_with_ciphertext
3445                    .unwrap()
3446                    .ciphertext_hi,
3447            )
3448        };
3449
3450        let new_decryptable_supply = account_info
3451            .new_decryptable_supply(mint_amount, supply_elgamal_keypair, aes_key)
3452            .map_err(|_| TokenError::AccountDecryption)?
3453            .into();
3454
3455        self.process_ixs(
3456            &confidential_mint_burn::instruction::confidential_mint_with_split_proofs(
3457                &self.program_id,
3458                destination_account,
3459                &self.pubkey,
3460                &mint_amount_auditor_ciphertext_lo,
3461                &mint_amount_auditor_ciphertext_hi,
3462                authority,
3463                &multisig_signers,
3464                equality_proof_location,
3465                ciphertext_validity_proof_location,
3466                range_proof_location,
3467                &new_decryptable_supply,
3468            )?,
3469            signing_keypairs,
3470        )
3471        .await
3472    }
3473
3474    /// Confidentially burn tokens
3475    #[allow(clippy::too_many_arguments)]
3476    pub async fn confidential_transfer_burn<S: Signers>(
3477        &self,
3478        authority: &Pubkey,
3479        source_account: &Pubkey,
3480        equality_proof_account: Option<&Pubkey>,
3481        ciphertext_validity_proof_account_with_ciphertext: Option<&ProofAccountWithCiphertext>,
3482        range_proof_account: Option<&Pubkey>,
3483        burn_amount: u64,
3484        source_elgamal_keypair: &ElGamalKeypair,
3485        supply_elgamal_pubkey: &ElGamalPubkey,
3486        auditor_elgamal_pubkey: Option<&ElGamalPubkey>,
3487        aes_key: &AeKey,
3488        account_info: Option<BurnAccountInfo>,
3489        signing_keypairs: &S,
3490    ) -> TokenResult<T::Output> {
3491        let signing_pubkeys = signing_keypairs.pubkeys();
3492        let multisig_signers = self.get_multisig_signers(authority, &signing_pubkeys);
3493
3494        let account_info = if let Some(account_info) = account_info {
3495            account_info
3496        } else {
3497            let account = self.get_account_info(source_account).await?;
3498            let confidential_supply_account =
3499                account.get_extension::<ConfidentialTransferAccount>()?;
3500            BurnAccountInfo::new(confidential_supply_account)
3501        };
3502
3503        let (equality_proof_data, ciphertext_validity_proof_data_with_ciphertext, range_proof_data) =
3504            if equality_proof_account.is_some()
3505                && ciphertext_validity_proof_account_with_ciphertext.is_some()
3506                && range_proof_account.is_some()
3507            {
3508                // if all proofs come from accounts, then skip proof generation
3509                (None, None, None)
3510            } else {
3511                let BurnProofData {
3512                    equality_proof_data,
3513                    ciphertext_validity_proof_data_with_ciphertext,
3514                    range_proof_data,
3515                } = account_info
3516                    .generate_split_burn_proof_data(
3517                        burn_amount,
3518                        source_elgamal_keypair,
3519                        aes_key,
3520                        supply_elgamal_pubkey,
3521                        auditor_elgamal_pubkey,
3522                    )
3523                    .map_err(|_| TokenError::ProofGeneration)?;
3524
3525                let equality_proof_data = equality_proof_account
3526                    .is_none()
3527                    .then_some(equality_proof_data);
3528                let ciphertext_validity_proof_data_with_ciphertext =
3529                    ciphertext_validity_proof_account_with_ciphertext
3530                        .is_none()
3531                        .then_some(ciphertext_validity_proof_data_with_ciphertext);
3532                let range_proof_data = range_proof_account.is_none().then_some(range_proof_data);
3533
3534                (
3535                    equality_proof_data,
3536                    ciphertext_validity_proof_data_with_ciphertext,
3537                    range_proof_data,
3538                )
3539            };
3540
3541        // cannot panic as long as either `proof_data` or `proof_account` is `Some(..)`,
3542        // which is guaranteed by the previous check
3543        let equality_proof_location = Self::confidential_transfer_create_proof_location(
3544            equality_proof_data.as_ref(),
3545            equality_proof_account,
3546            1,
3547        )
3548        .unwrap();
3549        let ciphertext_validity_proof_data =
3550            ciphertext_validity_proof_data_with_ciphertext.map(|data| data.proof_data);
3551        let ciphertext_validity_proof_location = Self::confidential_transfer_create_proof_location(
3552            ciphertext_validity_proof_data.as_ref(),
3553            ciphertext_validity_proof_account_with_ciphertext
3554                .map(|account| &account.context_state_account),
3555            2,
3556        )
3557        .unwrap();
3558        let range_proof_location = Self::confidential_transfer_create_proof_location(
3559            range_proof_data.as_ref(),
3560            range_proof_account,
3561            3,
3562        )
3563        .unwrap();
3564
3565        let (burn_amount_auditor_ciphertext_lo, burn_amount_auditor_ciphertext_hi) = if let Some(
3566            proof_data_with_ciphertext,
3567        ) =
3568            ciphertext_validity_proof_data_with_ciphertext
3569        {
3570            (
3571                proof_data_with_ciphertext.ciphertext_lo,
3572                proof_data_with_ciphertext.ciphertext_hi,
3573            )
3574        } else {
3575            // unwrap is safe as long as either `proof_data_with_ciphertext`,
3576            // `proof_account_with_ciphertext` is `Some(..)`, which is guaranteed by the
3577            // previous check
3578            (
3579                ciphertext_validity_proof_account_with_ciphertext
3580                    .unwrap()
3581                    .ciphertext_lo,
3582                ciphertext_validity_proof_account_with_ciphertext
3583                    .unwrap()
3584                    .ciphertext_hi,
3585            )
3586        };
3587
3588        let new_decryptable_balance = account_info
3589            .new_decryptable_balance(burn_amount, aes_key)
3590            .map_err(|_| TokenError::AccountDecryption)?
3591            .into();
3592
3593        self.process_ixs(
3594            &confidential_mint_burn::instruction::confidential_burn_with_split_proofs(
3595                &self.program_id,
3596                source_account,
3597                &self.pubkey,
3598                &new_decryptable_balance,
3599                &burn_amount_auditor_ciphertext_lo,
3600                &burn_amount_auditor_ciphertext_hi,
3601                authority,
3602                &multisig_signers,
3603                equality_proof_location,
3604                ciphertext_validity_proof_location,
3605                range_proof_location,
3606            )?,
3607            signing_keypairs,
3608        )
3609        .await
3610    }
3611
3612    /// Apply pending burn amount to the confidential supply amount
3613    pub async fn confidential_transfer_apply_pending_burn<S: Signers>(
3614        &self,
3615        authority: &Pubkey,
3616        signing_keypairs: &S,
3617    ) -> TokenResult<T::Output> {
3618        let signing_pubkeys = signing_keypairs.pubkeys();
3619        let multisig_signers = self.get_multisig_signers(authority, &signing_pubkeys);
3620
3621        self.process_ixs(
3622            &[confidential_mint_burn::instruction::apply_pending_burn(
3623                &self.program_id,
3624                &self.pubkey,
3625                authority,
3626                &multisig_signers,
3627            )?],
3628            signing_keypairs,
3629        )
3630        .await
3631    }
3632
3633    // Creates `ProofLocation` from proof data and context account. If both
3634    // `proof_data` and `context_account` are `None`, then the result is `None`.
3635    fn confidential_transfer_create_proof_location<'a, ZK: ZkProofData<U>, U: Pod>(
3636        proof_data: Option<&'a ZK>,
3637        context_account: Option<&'a Pubkey>,
3638        instruction_offset: i8,
3639    ) -> Option<ProofLocation<'a, ZK>> {
3640        if let Some(proof_data) = proof_data {
3641            Some(ProofLocation::InstructionOffset(
3642                instruction_offset.try_into().unwrap(),
3643                proof_data,
3644            ))
3645        } else {
3646            context_account.map(ProofLocation::ContextStateAccount)
3647        }
3648    }
3649
3650    pub async fn withdraw_excess_lamports<S: Signers>(
3651        &self,
3652        source: &Pubkey,
3653        destination: &Pubkey,
3654        authority: &Pubkey,
3655        signing_keypairs: &S,
3656    ) -> TokenResult<T::Output> {
3657        let signing_pubkeys = signing_keypairs.pubkeys();
3658        let multisig_signers = self.get_multisig_signers(authority, &signing_pubkeys);
3659
3660        self.process_ixs(
3661            &[spl_token_2022::instruction::withdraw_excess_lamports(
3662                &self.program_id,
3663                source,
3664                destination,
3665                authority,
3666                &multisig_signers,
3667            )?],
3668            signing_keypairs,
3669        )
3670        .await
3671    }
3672
3673    /// Initialize token-metadata on a mint
3674    pub async fn token_metadata_initialize<S: Signers>(
3675        &self,
3676        update_authority: &Pubkey,
3677        mint_authority: &Pubkey,
3678        name: String,
3679        symbol: String,
3680        uri: String,
3681        signing_keypairs: &S,
3682    ) -> TokenResult<T::Output> {
3683        self.process_ixs(
3684            &[spl_token_metadata_interface::instruction::initialize(
3685                &self.program_id,
3686                &self.pubkey,
3687                update_authority,
3688                &self.pubkey,
3689                mint_authority,
3690                name,
3691                symbol,
3692                uri,
3693            )],
3694            signing_keypairs,
3695        )
3696        .await
3697    }
3698
3699    async fn get_additional_rent_for_new_metadata(
3700        &self,
3701        token_metadata: &TokenMetadata,
3702    ) -> TokenResult<u64> {
3703        let account = self.get_account(self.pubkey).await?;
3704        let account_lamports = account.lamports;
3705        let mint_state = self.unpack_mint_info(account)?;
3706        let new_account_len = mint_state
3707            .try_get_new_account_len_for_variable_len_extension::<TokenMetadata>(token_metadata)?;
3708        let new_rent_exempt_minimum = self
3709            .client
3710            .get_minimum_balance_for_rent_exemption(new_account_len)
3711            .await
3712            .map_err(TokenError::Client)?;
3713        Ok(new_rent_exempt_minimum.saturating_sub(account_lamports))
3714    }
3715
3716    /// Initialize token-metadata on a mint
3717    #[allow(clippy::too_many_arguments)]
3718    pub async fn token_metadata_initialize_with_rent_transfer<S: Signers>(
3719        &self,
3720        payer: &Pubkey,
3721        update_authority: &Pubkey,
3722        mint_authority: &Pubkey,
3723        name: String,
3724        symbol: String,
3725        uri: String,
3726        signing_keypairs: &S,
3727    ) -> TokenResult<T::Output> {
3728        let token_metadata = TokenMetadata {
3729            name,
3730            symbol,
3731            uri,
3732            ..Default::default()
3733        };
3734        let additional_lamports = self
3735            .get_additional_rent_for_new_metadata(&token_metadata)
3736            .await?;
3737        let mut instructions = vec![];
3738        if additional_lamports > 0 {
3739            instructions.push(system_instruction::transfer(
3740                payer,
3741                &self.pubkey,
3742                additional_lamports,
3743            ));
3744        }
3745        instructions.push(spl_token_metadata_interface::instruction::initialize(
3746            &self.program_id,
3747            &self.pubkey,
3748            update_authority,
3749            &self.pubkey,
3750            mint_authority,
3751            token_metadata.name,
3752            token_metadata.symbol,
3753            token_metadata.uri,
3754        ));
3755        self.process_ixs(&instructions, signing_keypairs).await
3756    }
3757
3758    /// Update a token-metadata field on a mint
3759    pub async fn token_metadata_update_field<S: Signers>(
3760        &self,
3761        update_authority: &Pubkey,
3762        field: Field,
3763        value: String,
3764        signing_keypairs: &S,
3765    ) -> TokenResult<T::Output> {
3766        self.process_ixs(
3767            &[spl_token_metadata_interface::instruction::update_field(
3768                &self.program_id,
3769                &self.pubkey,
3770                update_authority,
3771                field,
3772                value,
3773            )],
3774            signing_keypairs,
3775        )
3776        .await
3777    }
3778
3779    async fn get_additional_rent_for_updated_metadata(
3780        &self,
3781        field: Field,
3782        value: String,
3783    ) -> TokenResult<u64> {
3784        let account = self.get_account(self.pubkey).await?;
3785        let account_lamports = account.lamports;
3786        let mint_state = self.unpack_mint_info(account)?;
3787        let mut token_metadata = mint_state.get_variable_len_extension::<TokenMetadata>()?;
3788        token_metadata.update(field, value);
3789        let new_account_len = mint_state
3790            .try_get_new_account_len_for_variable_len_extension::<TokenMetadata>(&token_metadata)?;
3791        let new_rent_exempt_minimum = self
3792            .client
3793            .get_minimum_balance_for_rent_exemption(new_account_len)
3794            .await
3795            .map_err(TokenError::Client)?;
3796        Ok(new_rent_exempt_minimum.saturating_sub(account_lamports))
3797    }
3798
3799    /// Update a token-metadata field on a mint. Includes a transfer for any
3800    /// additional rent-exempt SOL required.
3801    #[allow(clippy::too_many_arguments)]
3802    pub async fn token_metadata_update_field_with_rent_transfer<S: Signers>(
3803        &self,
3804        payer: &Pubkey,
3805        update_authority: &Pubkey,
3806        field: Field,
3807        value: String,
3808        transfer_lamports: Option<u64>,
3809        signing_keypairs: &S,
3810    ) -> TokenResult<T::Output> {
3811        let additional_lamports = if let Some(transfer_lamports) = transfer_lamports {
3812            transfer_lamports
3813        } else {
3814            self.get_additional_rent_for_updated_metadata(field.clone(), value.clone())
3815                .await?
3816        };
3817        let mut instructions = vec![];
3818        if additional_lamports > 0 {
3819            instructions.push(system_instruction::transfer(
3820                payer,
3821                &self.pubkey,
3822                additional_lamports,
3823            ));
3824        }
3825        instructions.push(spl_token_metadata_interface::instruction::update_field(
3826            &self.program_id,
3827            &self.pubkey,
3828            update_authority,
3829            field,
3830            value,
3831        ));
3832        self.process_ixs(&instructions, signing_keypairs).await
3833    }
3834
3835    /// Update the token-metadata authority in a mint
3836    pub async fn token_metadata_update_authority<S: Signers>(
3837        &self,
3838        current_authority: &Pubkey,
3839        new_authority: Option<Pubkey>,
3840        signing_keypairs: &S,
3841    ) -> TokenResult<T::Output> {
3842        self.process_ixs(
3843            &[spl_token_metadata_interface::instruction::update_authority(
3844                &self.program_id,
3845                &self.pubkey,
3846                current_authority,
3847                new_authority.try_into()?,
3848            )],
3849            signing_keypairs,
3850        )
3851        .await
3852    }
3853
3854    /// Remove a token-metadata field on a mint
3855    pub async fn token_metadata_remove_key<S: Signers>(
3856        &self,
3857        update_authority: &Pubkey,
3858        key: String,
3859        idempotent: bool,
3860        signing_keypairs: &S,
3861    ) -> TokenResult<T::Output> {
3862        self.process_ixs(
3863            &[spl_token_metadata_interface::instruction::remove_key(
3864                &self.program_id,
3865                &self.pubkey,
3866                update_authority,
3867                key,
3868                idempotent,
3869            )],
3870            signing_keypairs,
3871        )
3872        .await
3873    }
3874
3875    /// Initialize token-group on a mint
3876    pub async fn token_group_initialize<S: Signers>(
3877        &self,
3878        mint_authority: &Pubkey,
3879        update_authority: &Pubkey,
3880        max_size: u64,
3881        signing_keypairs: &S,
3882    ) -> TokenResult<T::Output> {
3883        self.process_ixs(
3884            &[spl_token_group_interface::instruction::initialize_group(
3885                &self.program_id,
3886                &self.pubkey,
3887                &self.pubkey,
3888                mint_authority,
3889                Some(*update_authority),
3890                max_size,
3891            )],
3892            signing_keypairs,
3893        )
3894        .await
3895    }
3896
3897    async fn get_additional_rent_for_fixed_len_extension<V: Extension + Pod>(
3898        &self,
3899    ) -> TokenResult<u64> {
3900        let account = self.get_account(self.pubkey).await?;
3901        let account_lamports = account.lamports;
3902        let mint_state = self.unpack_mint_info(account)?;
3903        if mint_state.get_extension::<V>().is_ok() {
3904            Ok(0)
3905        } else {
3906            let new_account_len = mint_state.try_get_new_account_len::<V>()?;
3907            let new_rent_exempt_minimum = self
3908                .client
3909                .get_minimum_balance_for_rent_exemption(new_account_len)
3910                .await
3911                .map_err(TokenError::Client)?;
3912            Ok(new_rent_exempt_minimum.saturating_sub(account_lamports))
3913        }
3914    }
3915
3916    /// Initialize token-group on a mint
3917    pub async fn token_group_initialize_with_rent_transfer<S: Signers>(
3918        &self,
3919        payer: &Pubkey,
3920        mint_authority: &Pubkey,
3921        update_authority: &Pubkey,
3922        max_size: u64,
3923        signing_keypairs: &S,
3924    ) -> TokenResult<T::Output> {
3925        let additional_lamports = self
3926            .get_additional_rent_for_fixed_len_extension::<TokenGroup>()
3927            .await?;
3928        let mut instructions = vec![];
3929        if additional_lamports > 0 {
3930            instructions.push(system_instruction::transfer(
3931                payer,
3932                &self.pubkey,
3933                additional_lamports,
3934            ));
3935        }
3936        instructions.push(spl_token_group_interface::instruction::initialize_group(
3937            &self.program_id,
3938            &self.pubkey,
3939            &self.pubkey,
3940            mint_authority,
3941            Some(*update_authority),
3942            max_size,
3943        ));
3944        self.process_ixs(&instructions, signing_keypairs).await
3945    }
3946
3947    /// Update a token-group max size on a mint
3948    pub async fn token_group_update_max_size<S: Signers>(
3949        &self,
3950        update_authority: &Pubkey,
3951        new_max_size: u64,
3952        signing_keypairs: &S,
3953    ) -> TokenResult<T::Output> {
3954        self.process_ixs(
3955            &[
3956                spl_token_group_interface::instruction::update_group_max_size(
3957                    &self.program_id,
3958                    &self.pubkey,
3959                    update_authority,
3960                    new_max_size,
3961                ),
3962            ],
3963            signing_keypairs,
3964        )
3965        .await
3966    }
3967
3968    /// Update the token-group authority in a mint
3969    pub async fn token_group_update_authority<S: Signers>(
3970        &self,
3971        current_authority: &Pubkey,
3972        new_authority: Option<Pubkey>,
3973        signing_keypairs: &S,
3974    ) -> TokenResult<T::Output> {
3975        self.process_ixs(
3976            &[
3977                spl_token_group_interface::instruction::update_group_authority(
3978                    &self.program_id,
3979                    &self.pubkey,
3980                    current_authority,
3981                    new_authority,
3982                ),
3983            ],
3984            signing_keypairs,
3985        )
3986        .await
3987    }
3988
3989    /// Initialize a token-group member on a mint
3990    pub async fn token_group_initialize_member<S: Signers>(
3991        &self,
3992        mint_authority: &Pubkey,
3993        group_mint: &Pubkey,
3994        group_update_authority: &Pubkey,
3995        signing_keypairs: &S,
3996    ) -> TokenResult<T::Output> {
3997        self.process_ixs(
3998            &[spl_token_group_interface::instruction::initialize_member(
3999                &self.program_id,
4000                &self.pubkey,
4001                &self.pubkey,
4002                mint_authority,
4003                group_mint,
4004                group_update_authority,
4005            )],
4006            signing_keypairs,
4007        )
4008        .await
4009    }
4010
4011    /// Initialize a token-group member on a mint
4012    #[allow(clippy::too_many_arguments)]
4013    pub async fn token_group_initialize_member_with_rent_transfer<S: Signers>(
4014        &self,
4015        payer: &Pubkey,
4016        mint_authority: &Pubkey,
4017        group_mint: &Pubkey,
4018        group_update_authority: &Pubkey,
4019        signing_keypairs: &S,
4020    ) -> TokenResult<T::Output> {
4021        let additional_lamports = self
4022            .get_additional_rent_for_fixed_len_extension::<TokenGroupMember>()
4023            .await?;
4024        let mut instructions = vec![];
4025        if additional_lamports > 0 {
4026            instructions.push(system_instruction::transfer(
4027                payer,
4028                &self.pubkey,
4029                additional_lamports,
4030            ));
4031        }
4032        instructions.push(spl_token_group_interface::instruction::initialize_member(
4033            &self.program_id,
4034            &self.pubkey,
4035            &self.pubkey,
4036            mint_authority,
4037            group_mint,
4038            group_update_authority,
4039        ));
4040        self.process_ixs(&instructions, signing_keypairs).await
4041    }
4042}
4043
4044/// Calculates the maximum chunk size for a zero-knowledge proof record
4045/// instruction to fit inside a single transaction.
4046fn calculate_record_max_chunk_size<F>(
4047    create_record_instructions: F,
4048    first_instruction: bool,
4049) -> usize
4050where
4051    F: Fn(bool, &[u8], u64) -> Vec<Instruction>,
4052{
4053    let ixs = create_record_instructions(first_instruction, &[], 0);
4054    let message = Message::new_with_blockhash(&ixs, Some(&Pubkey::default()), &Hash::default());
4055    let tx_size = bincode::serialized_size(&Transaction {
4056        signatures: vec![Signature::default(); message.header.num_required_signatures as usize],
4057        message,
4058    })
4059    .unwrap() as usize;
4060    PACKET_DATA_SIZE.saturating_sub(tx_size).saturating_sub(1)
4061}