Skip to main content

spl_token_client/
token.rs

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