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