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    /// Burn tokens from account with permissioned burn authority
1299    pub async fn permissioned_burn<S: Signers>(
1300        &self,
1301        source: &Address,
1302        permissioned_burn_authority: &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            [permissioned_burn::instruction::burn_checked(
1312                &self.program_id,
1313                source,
1314                &self.pubkey,
1315                permissioned_burn_authority,
1316                authority,
1317                &multisig_signers,
1318                amount,
1319                decimals,
1320            )?]
1321        } else {
1322            [permissioned_burn::instruction::burn(
1323                &self.program_id,
1324                source,
1325                &self.pubkey,
1326                permissioned_burn_authority,
1327                authority,
1328                &multisig_signers,
1329                amount,
1330            )?]
1331        };
1332
1333        self.process_ixs(&instructions, signing_keypairs).await
1334    }
1335
1336    /// Approve a delegate to spend tokens
1337    pub async fn approve<S: Signers>(
1338        &self,
1339        source: &Address,
1340        delegate: &Address,
1341        authority: &Address,
1342        amount: u64,
1343        signing_keypairs: &S,
1344    ) -> TokenResult<T::Output> {
1345        let signing_pubkeys = signing_keypairs.pubkeys();
1346        let multisig_signers = self.get_multisig_signers(authority, &signing_pubkeys);
1347
1348        let instructions = if let Some(decimals) = self.decimals {
1349            [instruction::approve_checked(
1350                &self.program_id,
1351                source,
1352                &self.pubkey,
1353                delegate,
1354                authority,
1355                &multisig_signers,
1356                amount,
1357                decimals,
1358            )?]
1359        } else {
1360            [instruction::approve(
1361                &self.program_id,
1362                source,
1363                delegate,
1364                authority,
1365                &multisig_signers,
1366                amount,
1367            )?]
1368        };
1369
1370        self.process_ixs(&instructions, signing_keypairs).await
1371    }
1372
1373    /// Revoke a delegate
1374    pub async fn revoke<S: Signers>(
1375        &self,
1376        source: &Address,
1377        authority: &Address,
1378        signing_keypairs: &S,
1379    ) -> TokenResult<T::Output> {
1380        let signing_pubkeys = signing_keypairs.pubkeys();
1381        let multisig_signers = self.get_multisig_signers(authority, &signing_pubkeys);
1382
1383        self.process_ixs(
1384            &[instruction::revoke(
1385                &self.program_id,
1386                source,
1387                authority,
1388                &multisig_signers,
1389            )?],
1390            signing_keypairs,
1391        )
1392        .await
1393    }
1394
1395    /// Close an empty account and reclaim its lamports
1396    pub async fn close_account<S: Signers>(
1397        &self,
1398        account: &Address,
1399        lamports_destination: &Address,
1400        authority: &Address,
1401        signing_keypairs: &S,
1402    ) -> TokenResult<T::Output> {
1403        let signing_pubkeys = signing_keypairs.pubkeys();
1404        let multisig_signers = self.get_multisig_signers(authority, &signing_pubkeys);
1405
1406        let mut instructions = vec![instruction::close_account(
1407            &self.program_id,
1408            account,
1409            lamports_destination,
1410            authority,
1411            &multisig_signers,
1412        )?];
1413
1414        if let Ok(Some(destination_account)) = self.client.get_account(*lamports_destination).await
1415        {
1416            if let Ok(destination_obj) =
1417                StateWithExtensionsOwned::<Account>::unpack(destination_account.data)
1418            {
1419                if destination_obj.base.is_native() {
1420                    instructions.push(instruction::sync_native(
1421                        &self.program_id,
1422                        lamports_destination,
1423                    )?);
1424                }
1425            }
1426        }
1427
1428        self.process_ixs(&instructions, signing_keypairs).await
1429    }
1430
1431    /// Close an account, reclaiming its lamports and tokens
1432    pub async fn empty_and_close_account<S: Signers>(
1433        &self,
1434        account_to_close: &Address,
1435        lamports_destination: &Address,
1436        tokens_destination: &Address,
1437        authority: &Address,
1438        signing_keypairs: &S,
1439    ) -> TokenResult<T::Output> {
1440        let signing_pubkeys = signing_keypairs.pubkeys();
1441        let multisig_signers = self.get_multisig_signers(authority, &signing_pubkeys);
1442
1443        // this implicitly validates that the mint on self is correct
1444        let account_state = self.get_account_info(account_to_close).await?;
1445
1446        let mut instructions = vec![];
1447
1448        if !self.is_native() && account_state.base.amount > 0 {
1449            // if a separate close authority is being used, it must be a delegate also
1450            if let Some(decimals) = self.decimals {
1451                instructions.push(instruction::transfer_checked(
1452                    &self.program_id,
1453                    account_to_close,
1454                    &self.pubkey,
1455                    tokens_destination,
1456                    authority,
1457                    &multisig_signers,
1458                    account_state.base.amount,
1459                    decimals,
1460                )?);
1461            } else {
1462                #[allow(deprecated)]
1463                instructions.push(instruction::transfer(
1464                    &self.program_id,
1465                    account_to_close,
1466                    tokens_destination,
1467                    authority,
1468                    &multisig_signers,
1469                    account_state.base.amount,
1470                )?);
1471            }
1472        }
1473
1474        instructions.push(instruction::close_account(
1475            &self.program_id,
1476            account_to_close,
1477            lamports_destination,
1478            authority,
1479            &multisig_signers,
1480        )?);
1481
1482        if let Ok(Some(destination_account)) = self.client.get_account(*lamports_destination).await
1483        {
1484            if let Ok(destination_obj) =
1485                StateWithExtensionsOwned::<Account>::unpack(destination_account.data)
1486            {
1487                if destination_obj.base.is_native() {
1488                    instructions.push(instruction::sync_native(
1489                        &self.program_id,
1490                        lamports_destination,
1491                    )?);
1492                }
1493            }
1494        }
1495
1496        self.process_ixs(&instructions, signing_keypairs).await
1497    }
1498
1499    /// Freeze a token account
1500    pub async fn freeze<S: Signers>(
1501        &self,
1502        account: &Address,
1503        authority: &Address,
1504        signing_keypairs: &S,
1505    ) -> TokenResult<T::Output> {
1506        let signing_pubkeys = signing_keypairs.pubkeys();
1507        let multisig_signers = self.get_multisig_signers(authority, &signing_pubkeys);
1508
1509        self.process_ixs(
1510            &[instruction::freeze_account(
1511                &self.program_id,
1512                account,
1513                &self.pubkey,
1514                authority,
1515                &multisig_signers,
1516            )?],
1517            signing_keypairs,
1518        )
1519        .await
1520    }
1521
1522    /// Thaw / unfreeze a token account
1523    pub async fn thaw<S: Signers>(
1524        &self,
1525        account: &Address,
1526        authority: &Address,
1527        signing_keypairs: &S,
1528    ) -> TokenResult<T::Output> {
1529        let signing_pubkeys = signing_keypairs.pubkeys();
1530        let multisig_signers = self.get_multisig_signers(authority, &signing_pubkeys);
1531
1532        self.process_ixs(
1533            &[instruction::thaw_account(
1534                &self.program_id,
1535                account,
1536                &self.pubkey,
1537                authority,
1538                &multisig_signers,
1539            )?],
1540            signing_keypairs,
1541        )
1542        .await
1543    }
1544
1545    /// Wrap lamports into native account
1546    pub async fn wrap<S: Signers>(
1547        &self,
1548        account: &Address,
1549        owner: &Address,
1550        lamports: u64,
1551        signing_keypairs: &S,
1552    ) -> TokenResult<T::Output> {
1553        // mutable owner for Tokenkeg, immutable otherwise
1554        let immutable_owner = self.program_id != spl_token_interface::id();
1555        let instructions = self.wrap_ixs(account, owner, lamports, immutable_owner)?;
1556
1557        self.process_ixs(&instructions, signing_keypairs).await
1558    }
1559
1560    /// Wrap lamports into a native account that can always have its ownership
1561    /// changed
1562    pub async fn wrap_with_mutable_ownership<S: Signers>(
1563        &self,
1564        account: &Address,
1565        owner: &Address,
1566        lamports: u64,
1567        signing_keypairs: &S,
1568    ) -> TokenResult<T::Output> {
1569        let instructions = self.wrap_ixs(account, owner, lamports, false)?;
1570
1571        self.process_ixs(&instructions, signing_keypairs).await
1572    }
1573
1574    fn wrap_ixs(
1575        &self,
1576        account: &Address,
1577        owner: &Address,
1578        lamports: u64,
1579        immutable_owner: bool,
1580    ) -> TokenResult<Vec<Instruction>> {
1581        if !self.is_native() {
1582            return Err(TokenError::AccountInvalidMint);
1583        }
1584
1585        let mut instructions = vec![];
1586        if *account == self.get_associated_token_address(owner) {
1587            instructions.push(system_instruction::transfer(owner, account, lamports));
1588            instructions.push(create_associated_token_account(
1589                &self.payer.pubkey(),
1590                owner,
1591                &self.pubkey,
1592                &self.program_id,
1593            ));
1594        } else {
1595            let extensions = if immutable_owner {
1596                vec![ExtensionType::ImmutableOwner]
1597            } else {
1598                vec![]
1599            };
1600            let space = ExtensionType::try_calculate_account_len::<Account>(&extensions)?;
1601
1602            instructions.push(system_instruction::create_account(
1603                &self.payer.pubkey(),
1604                account,
1605                lamports,
1606                space as u64,
1607                &self.program_id,
1608            ));
1609
1610            if immutable_owner {
1611                instructions.push(instruction::initialize_immutable_owner(
1612                    &self.program_id,
1613                    account,
1614                )?)
1615            }
1616
1617            instructions.push(instruction::initialize_account(
1618                &self.program_id,
1619                account,
1620                &self.pubkey,
1621                owner,
1622            )?);
1623        };
1624
1625        Ok(instructions)
1626    }
1627
1628    /// Unwrap lamports from native account
1629    pub async fn unwrap_lamports<S: Signers>(
1630        &self,
1631        source: &Address,
1632        destination: &Address,
1633        authority: &Address,
1634        amount: Option<u64>,
1635        signing_keypairs: &S,
1636    ) -> TokenResult<T::Output> {
1637        let signing_pubkeys = signing_keypairs.pubkeys();
1638        let multisig_signers = self.get_multisig_signers(authority, &signing_pubkeys);
1639
1640        self.process_ixs(
1641            &[instruction::unwrap_lamports(
1642                &self.program_id,
1643                source,
1644                destination,
1645                authority,
1646                &multisig_signers,
1647                amount,
1648            )?],
1649            signing_keypairs,
1650        )
1651        .await
1652    }
1653
1654    /// Sync native account lamports
1655    pub async fn sync_native(&self, account: &Address) -> TokenResult<T::Output> {
1656        self.process_ixs::<[&dyn Signer; 0]>(
1657            &[instruction::sync_native(&self.program_id, account)?],
1658            &[],
1659        )
1660        .await
1661    }
1662
1663    /// Set transfer fee
1664    pub async fn set_transfer_fee<S: Signers>(
1665        &self,
1666        authority: &Address,
1667        transfer_fee_basis_points: u16,
1668        maximum_fee: u64,
1669        signing_keypairs: &S,
1670    ) -> TokenResult<T::Output> {
1671        let signing_pubkeys = signing_keypairs.pubkeys();
1672        let multisig_signers = self.get_multisig_signers(authority, &signing_pubkeys);
1673
1674        self.process_ixs(
1675            &[transfer_fee::instruction::set_transfer_fee(
1676                &self.program_id,
1677                &self.pubkey,
1678                authority,
1679                &multisig_signers,
1680                transfer_fee_basis_points,
1681                maximum_fee,
1682            )?],
1683            signing_keypairs,
1684        )
1685        .await
1686    }
1687
1688    /// Set default account state on mint
1689    pub async fn set_default_account_state<S: Signers>(
1690        &self,
1691        authority: &Address,
1692        state: &AccountState,
1693        signing_keypairs: &S,
1694    ) -> TokenResult<T::Output> {
1695        let signing_pubkeys = signing_keypairs.pubkeys();
1696        let multisig_signers = self.get_multisig_signers(authority, &signing_pubkeys);
1697
1698        self.process_ixs(
1699            &[
1700                default_account_state::instruction::update_default_account_state(
1701                    &self.program_id,
1702                    &self.pubkey,
1703                    authority,
1704                    &multisig_signers,
1705                    state,
1706                )?,
1707            ],
1708            signing_keypairs,
1709        )
1710        .await
1711    }
1712
1713    /// Harvest withheld tokens to mint
1714    pub async fn harvest_withheld_tokens_to_mint(
1715        &self,
1716        sources: &[&Address],
1717    ) -> TokenResult<T::Output> {
1718        self.process_ixs::<[&dyn Signer; 0]>(
1719            &[transfer_fee::instruction::harvest_withheld_tokens_to_mint(
1720                &self.program_id,
1721                &self.pubkey,
1722                sources,
1723            )?],
1724            &[],
1725        )
1726        .await
1727    }
1728
1729    /// Withdraw withheld tokens from mint
1730    pub async fn withdraw_withheld_tokens_from_mint<S: Signers>(
1731        &self,
1732        destination: &Address,
1733        authority: &Address,
1734        signing_keypairs: &S,
1735    ) -> TokenResult<T::Output> {
1736        let signing_pubkeys = signing_keypairs.pubkeys();
1737        let multisig_signers = self.get_multisig_signers(authority, &signing_pubkeys);
1738
1739        self.process_ixs(
1740            &[
1741                transfer_fee::instruction::withdraw_withheld_tokens_from_mint(
1742                    &self.program_id,
1743                    &self.pubkey,
1744                    destination,
1745                    authority,
1746                    &multisig_signers,
1747                )?,
1748            ],
1749            signing_keypairs,
1750        )
1751        .await
1752    }
1753
1754    /// Withdraw withheld tokens from accounts
1755    pub async fn withdraw_withheld_tokens_from_accounts<S: Signers>(
1756        &self,
1757        destination: &Address,
1758        authority: &Address,
1759        sources: &[&Address],
1760        signing_keypairs: &S,
1761    ) -> TokenResult<T::Output> {
1762        let signing_pubkeys = signing_keypairs.pubkeys();
1763        let multisig_signers = self.get_multisig_signers(authority, &signing_pubkeys);
1764
1765        self.process_ixs(
1766            &[
1767                transfer_fee::instruction::withdraw_withheld_tokens_from_accounts(
1768                    &self.program_id,
1769                    &self.pubkey,
1770                    destination,
1771                    authority,
1772                    &multisig_signers,
1773                    sources,
1774                )?,
1775            ],
1776            signing_keypairs,
1777        )
1778        .await
1779    }
1780
1781    /// Reallocate a token account to be large enough for a set of
1782    /// `ExtensionType`s
1783    pub async fn reallocate<S: Signers>(
1784        &self,
1785        account: &Address,
1786        authority: &Address,
1787        extension_types: &[ExtensionType],
1788        signing_keypairs: &S,
1789    ) -> TokenResult<T::Output> {
1790        let signing_pubkeys = signing_keypairs.pubkeys();
1791        let multisig_signers = self.get_multisig_signers(authority, &signing_pubkeys);
1792
1793        self.process_ixs(
1794            &[instruction::reallocate(
1795                &self.program_id,
1796                account,
1797                &self.payer.pubkey(),
1798                authority,
1799                &multisig_signers,
1800                extension_types,
1801            )?],
1802            signing_keypairs,
1803        )
1804        .await
1805    }
1806
1807    /// Require memos on transfers into this account
1808    pub async fn enable_required_transfer_memos<S: Signers>(
1809        &self,
1810        account: &Address,
1811        authority: &Address,
1812        signing_keypairs: &S,
1813    ) -> TokenResult<T::Output> {
1814        let signing_pubkeys = signing_keypairs.pubkeys();
1815        let multisig_signers = self.get_multisig_signers(authority, &signing_pubkeys);
1816
1817        self.process_ixs(
1818            &[memo_transfer::instruction::enable_required_transfer_memos(
1819                &self.program_id,
1820                account,
1821                authority,
1822                &multisig_signers,
1823            )?],
1824            signing_keypairs,
1825        )
1826        .await
1827    }
1828
1829    /// Stop requiring memos on transfers into this account
1830    pub async fn disable_required_transfer_memos<S: Signers>(
1831        &self,
1832        account: &Address,
1833        authority: &Address,
1834        signing_keypairs: &S,
1835    ) -> TokenResult<T::Output> {
1836        let signing_pubkeys = signing_keypairs.pubkeys();
1837        let multisig_signers = self.get_multisig_signers(authority, &signing_pubkeys);
1838
1839        self.process_ixs(
1840            &[memo_transfer::instruction::disable_required_transfer_memos(
1841                &self.program_id,
1842                account,
1843                authority,
1844                &multisig_signers,
1845            )?],
1846            signing_keypairs,
1847        )
1848        .await
1849    }
1850
1851    /// Pause transferring, minting, and burning on the mint
1852    pub async fn pause<S: Signers>(
1853        &self,
1854        authority: &Address,
1855        signing_keypairs: &S,
1856    ) -> TokenResult<T::Output> {
1857        let signing_pubkeys = signing_keypairs.pubkeys();
1858        let multisig_signers = self.get_multisig_signers(authority, &signing_pubkeys);
1859
1860        self.process_ixs(
1861            &[pausable::instruction::pause(
1862                &self.program_id,
1863                self.get_address(),
1864                authority,
1865                &multisig_signers,
1866            )?],
1867            signing_keypairs,
1868        )
1869        .await
1870    }
1871
1872    /// Resume transferring, minting, and burning on the mint
1873    pub async fn resume<S: Signers>(
1874        &self,
1875        authority: &Address,
1876        signing_keypairs: &S,
1877    ) -> TokenResult<T::Output> {
1878        let signing_pubkeys = signing_keypairs.pubkeys();
1879        let multisig_signers = self.get_multisig_signers(authority, &signing_pubkeys);
1880
1881        self.process_ixs(
1882            &[pausable::instruction::resume(
1883                &self.program_id,
1884                self.get_address(),
1885                authority,
1886                &multisig_signers,
1887            )?],
1888            signing_keypairs,
1889        )
1890        .await
1891    }
1892
1893    /// Prevent unsafe usage of token account through CPI
1894    pub async fn enable_cpi_guard<S: Signers>(
1895        &self,
1896        account: &Address,
1897        authority: &Address,
1898        signing_keypairs: &S,
1899    ) -> TokenResult<T::Output> {
1900        let signing_pubkeys = signing_keypairs.pubkeys();
1901        let multisig_signers = self.get_multisig_signers(authority, &signing_pubkeys);
1902
1903        self.process_ixs(
1904            &[cpi_guard::instruction::enable_cpi_guard(
1905                &self.program_id,
1906                account,
1907                authority,
1908                &multisig_signers,
1909            )?],
1910            signing_keypairs,
1911        )
1912        .await
1913    }
1914
1915    /// Stop preventing unsafe usage of token account through CPI
1916    pub async fn disable_cpi_guard<S: Signers>(
1917        &self,
1918        account: &Address,
1919        authority: &Address,
1920        signing_keypairs: &S,
1921    ) -> TokenResult<T::Output> {
1922        let signing_pubkeys = signing_keypairs.pubkeys();
1923        let multisig_signers = self.get_multisig_signers(authority, &signing_pubkeys);
1924
1925        self.process_ixs(
1926            &[cpi_guard::instruction::disable_cpi_guard(
1927                &self.program_id,
1928                account,
1929                authority,
1930                &multisig_signers,
1931            )?],
1932            signing_keypairs,
1933        )
1934        .await
1935    }
1936
1937    /// Update interest rate
1938    pub async fn update_interest_rate<S: Signers>(
1939        &self,
1940        authority: &Address,
1941        new_rate: i16,
1942        signing_keypairs: &S,
1943    ) -> TokenResult<T::Output> {
1944        let signing_pubkeys = signing_keypairs.pubkeys();
1945        let multisig_signers = self.get_multisig_signers(authority, &signing_pubkeys);
1946
1947        self.process_ixs(
1948            &[interest_bearing_mint::instruction::update_rate(
1949                &self.program_id,
1950                self.get_address(),
1951                authority,
1952                &multisig_signers,
1953                new_rate,
1954            )?],
1955            signing_keypairs,
1956        )
1957        .await
1958    }
1959
1960    /// Update multiplier
1961    pub async fn update_multiplier<S: Signers>(
1962        &self,
1963        authority: &Address,
1964        new_multiplier: f64,
1965        new_multiplier_effective_timestamp: i64,
1966        signing_keypairs: &S,
1967    ) -> TokenResult<T::Output> {
1968        let signing_pubkeys = signing_keypairs.pubkeys();
1969        let multisig_signers = self.get_multisig_signers(authority, &signing_pubkeys);
1970
1971        self.process_ixs(
1972            &[scaled_ui_amount::instruction::update_multiplier(
1973                &self.program_id,
1974                self.get_address(),
1975                authority,
1976                &multisig_signers,
1977                new_multiplier,
1978                new_multiplier_effective_timestamp,
1979            )?],
1980            signing_keypairs,
1981        )
1982        .await
1983    }
1984
1985    /// Update transfer hook program id
1986    pub async fn update_transfer_hook_program_id<S: Signers>(
1987        &self,
1988        authority: &Address,
1989        new_program_id: Option<Address>,
1990        signing_keypairs: &S,
1991    ) -> TokenResult<T::Output> {
1992        let signing_pubkeys = signing_keypairs.pubkeys();
1993        let multisig_signers = self.get_multisig_signers(authority, &signing_pubkeys);
1994
1995        self.process_ixs(
1996            &[transfer_hook::instruction::update(
1997                &self.program_id,
1998                self.get_address(),
1999                authority,
2000                &multisig_signers,
2001                new_program_id,
2002            )?],
2003            signing_keypairs,
2004        )
2005        .await
2006    }
2007
2008    /// Update metadata pointer address
2009    pub async fn update_metadata_address<S: Signers>(
2010        &self,
2011        authority: &Address,
2012        new_metadata_address: Option<Address>,
2013        signing_keypairs: &S,
2014    ) -> TokenResult<T::Output> {
2015        let signing_pubkeys = signing_keypairs.pubkeys();
2016        let multisig_signers = self.get_multisig_signers(authority, &signing_pubkeys);
2017
2018        self.process_ixs(
2019            &[metadata_pointer::instruction::update(
2020                &self.program_id,
2021                self.get_address(),
2022                authority,
2023                &multisig_signers,
2024                new_metadata_address,
2025            )?],
2026            signing_keypairs,
2027        )
2028        .await
2029    }
2030
2031    /// Update group pointer address
2032    pub async fn update_group_address<S: Signers>(
2033        &self,
2034        authority: &Address,
2035        new_group_address: Option<Address>,
2036        signing_keypairs: &S,
2037    ) -> TokenResult<T::Output> {
2038        let signing_pubkeys = signing_keypairs.pubkeys();
2039        let multisig_signers = self.get_multisig_signers(authority, &signing_pubkeys);
2040
2041        self.process_ixs(
2042            &[group_pointer::instruction::update(
2043                &self.program_id,
2044                self.get_address(),
2045                authority,
2046                &multisig_signers,
2047                new_group_address,
2048            )?],
2049            signing_keypairs,
2050        )
2051        .await
2052    }
2053
2054    /// Update group member pointer address
2055    pub async fn update_group_member_address<S: Signers>(
2056        &self,
2057        authority: &Address,
2058        new_member_address: Option<Address>,
2059        signing_keypairs: &S,
2060    ) -> TokenResult<T::Output> {
2061        let signing_pubkeys = signing_keypairs.pubkeys();
2062        let multisig_signers = self.get_multisig_signers(authority, &signing_pubkeys);
2063
2064        self.process_ixs(
2065            &[group_member_pointer::instruction::update(
2066                &self.program_id,
2067                self.get_address(),
2068                authority,
2069                &multisig_signers,
2070                new_member_address,
2071            )?],
2072            signing_keypairs,
2073        )
2074        .await
2075    }
2076
2077    /// Update confidential transfer mint
2078    pub async fn confidential_transfer_update_mint<S: Signers>(
2079        &self,
2080        authority: &Address,
2081        auto_approve_new_account: bool,
2082        auditor_elgamal_pubkey: Option<PodElGamalPubkey>,
2083        signing_keypairs: &S,
2084    ) -> TokenResult<T::Output> {
2085        let signing_pubkeys = signing_keypairs.pubkeys();
2086        let multisig_signers = self.get_multisig_signers(authority, &signing_pubkeys);
2087
2088        self.process_ixs(
2089            &[confidential_transfer::instruction::update_mint(
2090                &self.program_id,
2091                &self.pubkey,
2092                authority,
2093                &multisig_signers,
2094                auto_approve_new_account,
2095                auditor_elgamal_pubkey,
2096            )?],
2097            signing_keypairs,
2098        )
2099        .await
2100    }
2101
2102    /// Configures confidential transfers for a token account. If the maximum
2103    /// pending balance credit counter for the extension is not provided,
2104    /// then it is set to be a default value of `2^16`.
2105    #[allow(clippy::too_many_arguments)]
2106    pub async fn confidential_transfer_configure_token_account<S: Signers>(
2107        &self,
2108        account: &Address,
2109        authority: &Address,
2110        context_state_account: Option<&Address>,
2111        maximum_pending_balance_credit_counter: Option<u64>,
2112        elgamal_keypair: &ElGamalKeypair,
2113        aes_key: &AeKey,
2114        signing_keypairs: &S,
2115    ) -> TokenResult<T::Output> {
2116        const DEFAULT_MAXIMUM_PENDING_BALANCE_CREDIT_COUNTER: u64 = 65536;
2117
2118        let signing_pubkeys = signing_keypairs.pubkeys();
2119        let multisig_signers = self.get_multisig_signers(authority, &signing_pubkeys);
2120
2121        let maximum_pending_balance_credit_counter = maximum_pending_balance_credit_counter
2122            .unwrap_or(DEFAULT_MAXIMUM_PENDING_BALANCE_CREDIT_COUNTER);
2123
2124        let proof_data = if context_state_account.is_some() {
2125            None
2126        } else {
2127            Some(
2128                build_pubkey_validity_proof_data(elgamal_keypair)
2129                    .map_err(|_| TokenError::ProofGeneration)?,
2130            )
2131        };
2132
2133        // cannot panic as long as either `proof_data` or `context_state_account` is `Some(..)`,
2134        // which is guaranteed by the previous check
2135        let proof_location = Self::confidential_transfer_create_proof_location(
2136            proof_data.as_ref(),
2137            context_state_account,
2138            1,
2139        )
2140        .unwrap();
2141
2142        let decryptable_balance = aes_key.encrypt(0).into();
2143
2144        self.process_ixs(
2145            &confidential_transfer::instruction::configure_account(
2146                &self.program_id,
2147                account,
2148                &self.pubkey,
2149                &decryptable_balance,
2150                maximum_pending_balance_credit_counter,
2151                authority,
2152                &multisig_signers,
2153                proof_location,
2154            )?,
2155            signing_keypairs,
2156        )
2157        .await
2158    }
2159
2160    /// Configures confidential transfers for a token account using an ElGamal
2161    /// registry account
2162    pub async fn confidential_transfer_configure_token_account_with_registry(
2163        &self,
2164        account: &Address,
2165        elgamal_registry_account: &Address,
2166        payer: Option<&Address>,
2167    ) -> TokenResult<T::Output> {
2168        self.process_ixs::<[&dyn Signer; 0]>(
2169            &[
2170                confidential_transfer::instruction::configure_account_with_registry(
2171                    &self.program_id,
2172                    account,
2173                    &self.pubkey,
2174                    elgamal_registry_account,
2175                    payer,
2176                )?,
2177            ],
2178            &[],
2179        )
2180        .await
2181    }
2182
2183    /// Approves a token account for confidential transfers
2184    pub async fn confidential_transfer_approve_account<S: Signers>(
2185        &self,
2186        account: &Address,
2187        authority: &Address,
2188        signing_keypairs: &S,
2189    ) -> TokenResult<T::Output> {
2190        let signing_pubkeys = signing_keypairs.pubkeys();
2191        let multisig_signers = self.get_multisig_signers(authority, &signing_pubkeys);
2192
2193        self.process_ixs(
2194            &[confidential_transfer::instruction::approve_account(
2195                &self.program_id,
2196                account,
2197                &self.pubkey,
2198                authority,
2199                &multisig_signers,
2200            )?],
2201            signing_keypairs,
2202        )
2203        .await
2204    }
2205
2206    /// Prepare a token account with the confidential transfer extension for
2207    /// closing
2208    pub async fn confidential_transfer_empty_account<S: Signers>(
2209        &self,
2210        account: &Address,
2211        authority: &Address,
2212        context_state_account: Option<&Address>,
2213        account_info: Option<EmptyAccountAccountInfo>,
2214        elgamal_keypair: &ElGamalKeypair,
2215        signing_keypairs: &S,
2216    ) -> TokenResult<T::Output> {
2217        let signing_pubkeys = signing_keypairs.pubkeys();
2218        let multisig_signers = self.get_multisig_signers(authority, &signing_pubkeys);
2219
2220        let account_info = if let Some(account_info) = account_info {
2221            account_info
2222        } else {
2223            let account = self.get_account_info(account).await?;
2224            let confidential_transfer_account =
2225                account.get_extension::<ConfidentialTransferAccount>()?;
2226            EmptyAccountAccountInfo::new(confidential_transfer_account)
2227        };
2228
2229        let proof_data = if context_state_account.is_some() {
2230            None
2231        } else {
2232            Some(
2233                account_info
2234                    .generate_proof_data(elgamal_keypair)
2235                    .map_err(|_| TokenError::ProofGeneration)?,
2236            )
2237        };
2238
2239        // cannot panic as long as either `proof_data` or `context_state_account` is `Some(..)`,
2240        // which is guaranteed by the previous check
2241        let proof_location = Self::confidential_transfer_create_proof_location(
2242            proof_data.as_ref(),
2243            context_state_account,
2244            1,
2245        )
2246        .unwrap();
2247
2248        self.process_ixs(
2249            &confidential_transfer::instruction::empty_account(
2250                &self.program_id,
2251                account,
2252                authority,
2253                &multisig_signers,
2254                proof_location,
2255            )?,
2256            signing_keypairs,
2257        )
2258        .await
2259    }
2260
2261    /// Deposit SPL Tokens into the pending balance of a confidential token
2262    /// account
2263    pub async fn confidential_transfer_deposit<S: Signers>(
2264        &self,
2265        account: &Address,
2266        authority: &Address,
2267        amount: u64,
2268        decimals: u8,
2269        signing_keypairs: &S,
2270    ) -> TokenResult<T::Output> {
2271        let signing_pubkeys = signing_keypairs.pubkeys();
2272        let multisig_signers = self.get_multisig_signers(authority, &signing_pubkeys);
2273
2274        self.process_ixs(
2275            &[confidential_transfer::instruction::deposit(
2276                &self.program_id,
2277                account,
2278                &self.pubkey,
2279                amount,
2280                decimals,
2281                authority,
2282                &multisig_signers,
2283            )?],
2284            signing_keypairs,
2285        )
2286        .await
2287    }
2288
2289    /// Withdraw SPL Tokens from the available balance of a confidential token
2290    /// account
2291    #[allow(clippy::too_many_arguments)]
2292    pub async fn confidential_transfer_withdraw<S: Signers>(
2293        &self,
2294        account: &Address,
2295        authority: &Address,
2296        equality_proof_account: Option<&Address>,
2297        range_proof_account: Option<&Address>,
2298        withdraw_amount: u64,
2299        decimals: u8,
2300        account_info: Option<WithdrawAccountInfo>,
2301        elgamal_keypair: &ElGamalKeypair,
2302        aes_key: &AeKey,
2303        signing_keypairs: &S,
2304    ) -> TokenResult<T::Output> {
2305        let signing_pubkeys = signing_keypairs.pubkeys();
2306        let multisig_signers = self.get_multisig_signers(authority, &signing_pubkeys);
2307
2308        let account_info = if let Some(account_info) = account_info {
2309            account_info
2310        } else {
2311            let account = self.get_account_info(account).await?;
2312            let confidential_transfer_account =
2313                account.get_extension::<ConfidentialTransferAccount>()?;
2314            WithdrawAccountInfo::new(confidential_transfer_account)
2315        };
2316
2317        let (equality_proof_data, range_proof_data) =
2318            if equality_proof_account.is_some() && range_proof_account.is_some() {
2319                (None, None)
2320            } else {
2321                let WithdrawProofData {
2322                    equality_proof_data,
2323                    range_proof_data,
2324                } = account_info
2325                    .generate_proof_data(withdraw_amount, elgamal_keypair, aes_key)
2326                    .map_err(|_| TokenError::ProofGeneration)?;
2327
2328                // if proof accounts are none, then proof data must be included as instruction
2329                // data
2330                let equality_proof_data = equality_proof_account
2331                    .is_none()
2332                    .then_some(equality_proof_data);
2333                let range_proof_data = range_proof_account.is_none().then_some(range_proof_data);
2334
2335                (equality_proof_data, range_proof_data)
2336            };
2337
2338        // cannot panic as long as either `proof_data` or `proof_account` is `Some(..)`,
2339        // which is guaranteed by the previous check
2340        let equality_proof_location = Self::confidential_transfer_create_proof_location(
2341            equality_proof_data.as_ref(),
2342            equality_proof_account,
2343            1,
2344        )
2345        .unwrap();
2346
2347        let range_proof_location = Self::confidential_transfer_create_proof_location(
2348            range_proof_data.as_ref(),
2349            range_proof_account,
2350            2,
2351        )
2352        .unwrap();
2353
2354        let new_decryptable_available_balance = account_info
2355            .new_decryptable_available_balance(withdraw_amount, aes_key)
2356            .map_err(|_| TokenError::AccountDecryption)?
2357            .into();
2358
2359        self.process_ixs(
2360            &confidential_transfer::instruction::withdraw(
2361                &self.program_id,
2362                account,
2363                &self.pubkey,
2364                withdraw_amount,
2365                decimals,
2366                &new_decryptable_available_balance,
2367                authority,
2368                &multisig_signers,
2369                equality_proof_location,
2370                range_proof_location,
2371            )?,
2372            signing_keypairs,
2373        )
2374        .await
2375    }
2376
2377    /// Transfer tokens confidentially
2378    #[allow(clippy::too_many_arguments)]
2379    pub async fn confidential_transfer_transfer<S: Signers>(
2380        &self,
2381        source_account: &Address,
2382        destination_account: &Address,
2383        source_authority: &Address,
2384        equality_proof_account: Option<&Address>,
2385        ciphertext_validity_proof_account_with_ciphertext: Option<&ProofAccountWithCiphertext>,
2386        range_proof_account: Option<&Address>,
2387        transfer_amount: u64,
2388        account_info: Option<TransferAccountInfo>,
2389        source_elgamal_keypair: &ElGamalKeypair,
2390        source_aes_key: &AeKey,
2391        destination_elgamal_pubkey: &ElGamalPubkey,
2392        auditor_elgamal_pubkey: Option<&ElGamalPubkey>,
2393        signing_keypairs: &S,
2394    ) -> TokenResult<T::Output> {
2395        let signing_pubkeys = signing_keypairs.pubkeys();
2396        let multisig_signers = self.get_multisig_signers(source_authority, &signing_pubkeys);
2397
2398        let account_info = if let Some(account_info) = account_info {
2399            account_info
2400        } else {
2401            let account = self.get_account_info(source_account).await?;
2402            let confidential_transfer_account =
2403                account.get_extension::<ConfidentialTransferAccount>()?;
2404            TransferAccountInfo::new(confidential_transfer_account)
2405        };
2406
2407        let (equality_proof_data, ciphertext_validity_proof_data_with_ciphertext, range_proof_data) =
2408            if equality_proof_account.is_some()
2409                && ciphertext_validity_proof_account_with_ciphertext.is_some()
2410                && range_proof_account.is_some()
2411            {
2412                (None, None, None)
2413            } else {
2414                let TransferProofData {
2415                    equality_proof_data,
2416                    ciphertext_validity_proof_data_with_ciphertext,
2417                    range_proof_data,
2418                } = account_info
2419                    .generate_split_transfer_proof_data(
2420                        transfer_amount,
2421                        source_elgamal_keypair,
2422                        source_aes_key,
2423                        destination_elgamal_pubkey,
2424                        auditor_elgamal_pubkey,
2425                    )
2426                    .map_err(|_| TokenError::ProofGeneration)?;
2427
2428                // if proof accounts are none, then proof data must be included as instruction
2429                // data
2430                let equality_proof_data = equality_proof_account
2431                    .is_none()
2432                    .then_some(equality_proof_data);
2433                let ciphertext_validity_proof_data_with_ciphertext =
2434                    ciphertext_validity_proof_account_with_ciphertext
2435                        .is_none()
2436                        .then_some(ciphertext_validity_proof_data_with_ciphertext);
2437                let range_proof_data = range_proof_account.is_none().then_some(range_proof_data);
2438
2439                (
2440                    equality_proof_data,
2441                    ciphertext_validity_proof_data_with_ciphertext,
2442                    range_proof_data,
2443                )
2444            };
2445
2446        let (transfer_amount_auditor_ciphertext_lo, transfer_amount_auditor_ciphertext_hi) =
2447            if let Some(proof_data_with_ciphertext) = ciphertext_validity_proof_data_with_ciphertext
2448            {
2449                (
2450                    proof_data_with_ciphertext.ciphertext_lo,
2451                    proof_data_with_ciphertext.ciphertext_hi,
2452                )
2453            } else {
2454                // unwrap is safe as long as either `proof_data_with_ciphertext`,
2455                // `proof_account_with_ciphertext` is `Some(..)`, which is guaranteed by the
2456                // previous check
2457                (
2458                    ciphertext_validity_proof_account_with_ciphertext
2459                        .unwrap()
2460                        .ciphertext_lo,
2461                    ciphertext_validity_proof_account_with_ciphertext
2462                        .unwrap()
2463                        .ciphertext_hi,
2464                )
2465            };
2466
2467        // cannot panic as long as either `proof_data` or `proof_account` is `Some(..)`,
2468        // which is guaranteed by the previous check
2469        let equality_proof_location = Self::confidential_transfer_create_proof_location(
2470            equality_proof_data.as_ref(),
2471            equality_proof_account,
2472            1,
2473        )
2474        .unwrap();
2475        let ciphertext_validity_proof_data =
2476            ciphertext_validity_proof_data_with_ciphertext.map(|data| data.proof_data);
2477        let ciphertext_validity_proof_location = Self::confidential_transfer_create_proof_location(
2478            ciphertext_validity_proof_data.as_ref(),
2479            ciphertext_validity_proof_account_with_ciphertext
2480                .map(|account| &account.context_state_account),
2481            2,
2482        )
2483        .unwrap();
2484        let range_proof_location = Self::confidential_transfer_create_proof_location(
2485            range_proof_data.as_ref(),
2486            range_proof_account,
2487            3,
2488        )
2489        .unwrap();
2490
2491        let new_decryptable_available_balance = account_info
2492            .new_decryptable_available_balance(transfer_amount, source_aes_key)
2493            .map_err(|_| TokenError::AccountDecryption)?
2494            .into();
2495
2496        let mut instructions = confidential_transfer::instruction::transfer(
2497            &self.program_id,
2498            source_account,
2499            self.get_address(),
2500            destination_account,
2501            &new_decryptable_available_balance,
2502            &transfer_amount_auditor_ciphertext_lo,
2503            &transfer_amount_auditor_ciphertext_hi,
2504            source_authority,
2505            &multisig_signers,
2506            equality_proof_location,
2507            ciphertext_validity_proof_location,
2508            range_proof_location,
2509        )?;
2510        offchain::add_extra_account_metas(
2511            &mut instructions[0],
2512            source_account,
2513            self.get_address(),
2514            destination_account,
2515            source_authority,
2516            u64::MAX,
2517            |address| {
2518                self.client
2519                    .get_account(address)
2520                    .map_ok(|opt| opt.map(|acc| acc.data))
2521            },
2522        )
2523        .await
2524        .map_err(|_| TokenError::AccountNotFound)?;
2525        self.process_ixs(&instructions, signing_keypairs).await
2526    }
2527
2528    /// Create a record account containing zero-knowledge proof needed for a
2529    /// confidential transfer.
2530    pub async fn confidential_transfer_create_record_account<
2531        S1: Signer,
2532        S2: Signer,
2533        ZK: Pod + ZkProofData<U>,
2534        U: Pod,
2535    >(
2536        &self,
2537        record_account: &Address,
2538        record_authority: &Address,
2539        proof_data: &ZK,
2540        record_account_signer: &S1,
2541        record_authority_signer: &S2,
2542    ) -> TokenResult<Vec<T::Output>> {
2543        let proof_data = bytes_of(proof_data);
2544        let space = proof_data
2545            .len()
2546            .saturating_add(RecordData::WRITABLE_START_INDEX);
2547        let rent = self
2548            .client
2549            .get_minimum_balance_for_rent_exemption(space)
2550            .await
2551            .map_err(TokenError::Client)?;
2552
2553        // A closure that constructs a vector of instructions needed to create and write
2554        // to record accounts. The closure is defined as a convenience function
2555        // to be fed into the function `calculate_record_max_chunk_size`.
2556        let create_record_instructions = |first_instruction: bool, bytes: &[u8], offset: u64| {
2557            let mut ixs = vec![];
2558            if first_instruction {
2559                ixs.push(system_instruction::create_account(
2560                    &self.payer.pubkey(),
2561                    record_account,
2562                    rent,
2563                    space as u64,
2564                    &spl_record::id(),
2565                ));
2566                ixs.push(spl_record::instruction::initialize(
2567                    record_account,
2568                    record_authority,
2569                ));
2570            }
2571            ixs.push(spl_record::instruction::write(
2572                record_account,
2573                record_authority,
2574                offset,
2575                bytes,
2576            ));
2577            ixs
2578        };
2579        let first_chunk_size = calculate_record_max_chunk_size(create_record_instructions, true);
2580        let (first_chunk, rest) = if space <= first_chunk_size {
2581            (proof_data, &[] as &[u8])
2582        } else {
2583            proof_data.split_at(first_chunk_size)
2584        };
2585
2586        let first_ixs = create_record_instructions(true, first_chunk, 0);
2587        let first_ixs_signers: [&dyn Signer; 2] = [record_account_signer, record_authority_signer];
2588        self.process_ixs(&first_ixs, &first_ixs_signers).await?;
2589
2590        let subsequent_chunk_size =
2591            calculate_record_max_chunk_size(create_record_instructions, false);
2592        let mut record_offset = first_chunk_size;
2593        let mut ixs_batch = vec![];
2594        for chunk in rest.chunks(subsequent_chunk_size) {
2595            ixs_batch.push(create_record_instructions(
2596                false,
2597                chunk,
2598                record_offset as u64,
2599            ));
2600            record_offset = record_offset.saturating_add(chunk.len());
2601        }
2602
2603        let futures = ixs_batch
2604            .into_iter()
2605            .map(|ixs| async move { self.process_ixs(&ixs, &[record_authority_signer]).await })
2606            .collect::<Vec<_>>();
2607
2608        join_all(futures).await.into_iter().collect()
2609    }
2610
2611    /// Close a record account.
2612    pub async fn confidential_transfer_close_record_account<S: Signers>(
2613        &self,
2614        record_account: &Address,
2615        lamport_destination_account: &Address,
2616        record_account_authority: &Address,
2617        signing_keypairs: &S,
2618    ) -> TokenResult<T::Output> {
2619        self.process_ixs(
2620            &[spl_record::instruction::close_account(
2621                record_account,
2622                record_account_authority,
2623                lamport_destination_account,
2624            )],
2625            signing_keypairs,
2626        )
2627        .await
2628    }
2629
2630    /// Create a context state account containing zero-knowledge proof needed
2631    /// for a confidential transfer instruction.
2632    pub async fn confidential_transfer_create_context_state_account<
2633        S: Signers,
2634        ZK: Pod + ZkProofData<U>,
2635        U: Pod,
2636    >(
2637        &self,
2638        context_state_account: &Address,
2639        context_state_authority: &Address,
2640        proof_data: &ZK,
2641        split_account_creation_and_proof_verification: bool,
2642        signing_keypairs: &S,
2643    ) -> TokenResult<T::Output> {
2644        let instruction_type = zk_proof_type_to_instruction(ZK::PROOF_TYPE)?;
2645        let space = size_of::<ProofContextState<U>>();
2646        let rent = self
2647            .client
2648            .get_minimum_balance_for_rent_exemption(space)
2649            .await
2650            .map_err(TokenError::Client)?;
2651
2652        let context_state_info = ContextStateInfo {
2653            context_state_account,
2654            context_state_authority,
2655        };
2656
2657        // Some proof instructions are right at the transaction size limit, but in the
2658        // future it might be able to support the transfer too
2659        if split_account_creation_and_proof_verification {
2660            self.process_ixs(
2661                &[system_instruction::create_account(
2662                    &self.payer.pubkey(),
2663                    context_state_account,
2664                    rent,
2665                    space as u64,
2666                    &zk_elgamal_proof_program::id(),
2667                )],
2668                signing_keypairs,
2669            )
2670            .await?;
2671
2672            let blockhash = self
2673                .client
2674                .get_latest_blockhash()
2675                .await
2676                .map_err(TokenError::Client)?;
2677
2678            let transaction = Transaction::new_signed_with_payer(
2679                &[instruction_type.encode_verify_proof(Some(context_state_info), proof_data)],
2680                Some(&self.payer.pubkey()),
2681                &[self.payer.as_ref()],
2682                blockhash,
2683            );
2684
2685            self.client
2686                .send_transaction(&transaction)
2687                .await
2688                .map_err(TokenError::Client)
2689        } else {
2690            self.process_ixs(
2691                &[
2692                    system_instruction::create_account(
2693                        &self.payer.pubkey(),
2694                        context_state_account,
2695                        rent,
2696                        space as u64,
2697                        &zk_elgamal_proof_program::id(),
2698                    ),
2699                    instruction_type.encode_verify_proof(Some(context_state_info), proof_data),
2700                ],
2701                signing_keypairs,
2702            )
2703            .await
2704        }
2705    }
2706
2707    /// Create a context state account from another account containing
2708    /// zero-knowledge proof needed for a confidential transfer instruction.
2709    pub async fn confidential_transfer_create_context_state_account_from_record<
2710        S: Signers,
2711        ZK: Pod + ZkProofData<U>,
2712        U: Pod,
2713    >(
2714        &self,
2715        context_state_account: &Address,
2716        context_state_authority: &Address,
2717        record_account: &Address,
2718        signing_keypairs: &S,
2719    ) -> TokenResult<T::Output> {
2720        const RECORD_ACCOUNT_PROOF_OFFSET: u32 = 33;
2721
2722        let instruction_type = zk_proof_type_to_instruction(ZK::PROOF_TYPE)?;
2723        let space = size_of::<ProofContextState<U>>();
2724        let rent = self
2725            .client
2726            .get_minimum_balance_for_rent_exemption(space)
2727            .await
2728            .map_err(TokenError::Client)?;
2729
2730        let context_state_info = ContextStateInfo {
2731            context_state_account,
2732            context_state_authority,
2733        };
2734
2735        // Some proof instructions are right at the transaction size limit, but in the
2736        // future it might be able to support the transfer too
2737        self.process_ixs(
2738            &[
2739                system_instruction::create_account(
2740                    &self.payer.pubkey(),
2741                    context_state_account,
2742                    rent,
2743                    space as u64,
2744                    &zk_elgamal_proof_program::id(),
2745                ),
2746                instruction_type.encode_verify_proof_from_account(
2747                    Some(context_state_info),
2748                    record_account,
2749                    RECORD_ACCOUNT_PROOF_OFFSET,
2750                ),
2751            ],
2752            signing_keypairs,
2753        )
2754        .await
2755    }
2756
2757    /// Close a ZK Token proof program context state
2758    pub async fn confidential_transfer_close_context_state_account<S: Signers>(
2759        &self,
2760        context_state_account: &Address,
2761        lamport_destination_account: &Address,
2762        context_state_authority: &Address,
2763        signing_keypairs: &S,
2764    ) -> TokenResult<T::Output> {
2765        let context_state_info = ContextStateInfo {
2766            context_state_account,
2767            context_state_authority,
2768        };
2769
2770        self.process_ixs(
2771            &[close_context_state(
2772                context_state_info,
2773                lamport_destination_account,
2774            )],
2775            signing_keypairs,
2776        )
2777        .await
2778    }
2779
2780    /// Transfer tokens confidentially with fee
2781    #[allow(clippy::too_many_arguments)]
2782    pub async fn confidential_transfer_transfer_with_fee<S: Signers>(
2783        &self,
2784        source_account: &Address,
2785        destination_account: &Address,
2786        source_authority: &Address,
2787        equality_proof_account: Option<&Address>,
2788        transfer_amount_ciphertext_validity_proof_account_with_ciphertext: Option<
2789            &ProofAccountWithCiphertext,
2790        >,
2791        percentage_with_cap_proof_account: Option<&Address>,
2792        fee_ciphertext_validity_proof_account: Option<&Address>,
2793        range_proof_account: Option<&Address>,
2794        transfer_amount: u64,
2795        account_info: Option<TransferAccountInfo>,
2796        source_elgamal_keypair: &ElGamalKeypair,
2797        source_aes_key: &AeKey,
2798        destination_elgamal_pubkey: &ElGamalPubkey,
2799        auditor_elgamal_pubkey: Option<&ElGamalPubkey>,
2800        withdraw_withheld_authority_elgamal_pubkey: &ElGamalPubkey,
2801        fee_rate_basis_points: u16,
2802        maximum_fee: u64,
2803        signing_keypairs: &S,
2804    ) -> TokenResult<T::Output> {
2805        let signing_pubkeys = signing_keypairs.pubkeys();
2806        let multisig_signers = self.get_multisig_signers(source_authority, &signing_pubkeys);
2807
2808        let account_info = if let Some(account_info) = account_info {
2809            account_info
2810        } else {
2811            let account = self.get_account_info(source_account).await?;
2812            let confidential_transfer_account =
2813                account.get_extension::<ConfidentialTransferAccount>()?;
2814            TransferAccountInfo::new(confidential_transfer_account)
2815        };
2816
2817        let (
2818            equality_proof_data,
2819            transfer_amount_ciphertext_validity_proof_data_with_ciphertext,
2820            percentage_with_cap_proof_data,
2821            fee_ciphertext_validity_proof_data,
2822            range_proof_data,
2823        ) = if equality_proof_account.is_some()
2824            && transfer_amount_ciphertext_validity_proof_account_with_ciphertext.is_some()
2825            && percentage_with_cap_proof_account.is_some()
2826            && fee_ciphertext_validity_proof_account.is_some()
2827            && range_proof_account.is_some()
2828        {
2829            // if all proofs come from accounts, then skip proof generation
2830            (None, None, None, None, None)
2831        } else {
2832            let TransferWithFeeProofData {
2833                equality_proof_data,
2834                transfer_amount_ciphertext_validity_proof_data_with_ciphertext,
2835                percentage_with_cap_proof_data,
2836                fee_ciphertext_validity_proof_data,
2837                range_proof_data,
2838            } = account_info
2839                .generate_split_transfer_with_fee_proof_data(
2840                    transfer_amount,
2841                    source_elgamal_keypair,
2842                    source_aes_key,
2843                    destination_elgamal_pubkey,
2844                    auditor_elgamal_pubkey,
2845                    withdraw_withheld_authority_elgamal_pubkey,
2846                    fee_rate_basis_points,
2847                    maximum_fee,
2848                )
2849                .map_err(|_| TokenError::ProofGeneration)?;
2850
2851            let equality_proof_data = equality_proof_account
2852                .is_none()
2853                .then_some(equality_proof_data);
2854            let transfer_amount_ciphertext_validity_proof_data_with_ciphertext =
2855                transfer_amount_ciphertext_validity_proof_account_with_ciphertext
2856                    .is_none()
2857                    .then_some(transfer_amount_ciphertext_validity_proof_data_with_ciphertext);
2858            let percentage_with_cap_proof_data = percentage_with_cap_proof_account
2859                .is_none()
2860                .then_some(percentage_with_cap_proof_data);
2861            let fee_ciphertext_validity_proof_data = fee_ciphertext_validity_proof_account
2862                .is_none()
2863                .then_some(fee_ciphertext_validity_proof_data);
2864            let range_proof_data = range_proof_account.is_none().then_some(range_proof_data);
2865
2866            (
2867                equality_proof_data,
2868                transfer_amount_ciphertext_validity_proof_data_with_ciphertext,
2869                percentage_with_cap_proof_data,
2870                fee_ciphertext_validity_proof_data,
2871                range_proof_data,
2872            )
2873        };
2874
2875        let (transfer_amount_auditor_ciphertext_lo, transfer_amount_auditor_ciphertext_hi) =
2876            if let Some(proof_data_with_ciphertext) =
2877                transfer_amount_ciphertext_validity_proof_data_with_ciphertext
2878            {
2879                (
2880                    proof_data_with_ciphertext.ciphertext_lo,
2881                    proof_data_with_ciphertext.ciphertext_hi,
2882                )
2883            } else {
2884                // unwrap is safe as long as either `proof_data_with_ciphertext`,
2885                // `proof_account_with_ciphertext` is `Some(..)`, which is guaranteed by the
2886                // previous check
2887                (
2888                    transfer_amount_ciphertext_validity_proof_account_with_ciphertext
2889                        .unwrap()
2890                        .ciphertext_lo,
2891                    transfer_amount_ciphertext_validity_proof_account_with_ciphertext
2892                        .unwrap()
2893                        .ciphertext_hi,
2894                )
2895            };
2896
2897        // cannot panic as long as either `proof_data` or `proof_account` is `Some(..)`,
2898        // which is guaranteed by the previous check
2899        let equality_proof_location = Self::confidential_transfer_create_proof_location(
2900            equality_proof_data.as_ref(),
2901            equality_proof_account,
2902            1,
2903        )
2904        .unwrap();
2905        let transfer_amount_ciphertext_validity_proof_data =
2906            transfer_amount_ciphertext_validity_proof_data_with_ciphertext
2907                .map(|data| data.proof_data);
2908        let transfer_amount_ciphertext_validity_proof_location =
2909            Self::confidential_transfer_create_proof_location(
2910                transfer_amount_ciphertext_validity_proof_data.as_ref(),
2911                transfer_amount_ciphertext_validity_proof_account_with_ciphertext
2912                    .map(|account| &account.context_state_account),
2913                2,
2914            )
2915            .unwrap();
2916        let fee_sigma_proof_location = Self::confidential_transfer_create_proof_location(
2917            percentage_with_cap_proof_data.as_ref(),
2918            percentage_with_cap_proof_account,
2919            3,
2920        )
2921        .unwrap();
2922        let fee_ciphertext_validity_proof_location =
2923            Self::confidential_transfer_create_proof_location(
2924                fee_ciphertext_validity_proof_data.as_ref(),
2925                fee_ciphertext_validity_proof_account,
2926                4,
2927            )
2928            .unwrap();
2929        let range_proof_location = Self::confidential_transfer_create_proof_location(
2930            range_proof_data.as_ref(),
2931            range_proof_account,
2932            5,
2933        )
2934        .unwrap();
2935
2936        let new_decryptable_available_balance = account_info
2937            .new_decryptable_available_balance(transfer_amount, source_aes_key)
2938            .map_err(|_| TokenError::AccountDecryption)?
2939            .into();
2940
2941        let mut instructions = confidential_transfer::instruction::transfer_with_fee(
2942            &self.program_id,
2943            source_account,
2944            self.get_address(),
2945            destination_account,
2946            &new_decryptable_available_balance,
2947            &transfer_amount_auditor_ciphertext_lo,
2948            &transfer_amount_auditor_ciphertext_hi,
2949            source_authority,
2950            &multisig_signers,
2951            equality_proof_location,
2952            transfer_amount_ciphertext_validity_proof_location,
2953            fee_sigma_proof_location,
2954            fee_ciphertext_validity_proof_location,
2955            range_proof_location,
2956        )?;
2957        offchain::add_extra_account_metas(
2958            &mut instructions[0],
2959            source_account,
2960            self.get_address(),
2961            destination_account,
2962            source_authority,
2963            u64::MAX,
2964            |address| {
2965                self.client
2966                    .get_account(address)
2967                    .map_ok(|opt| opt.map(|acc| acc.data))
2968            },
2969        )
2970        .await
2971        .map_err(|_| TokenError::AccountNotFound)?;
2972        self.process_ixs(&instructions, signing_keypairs).await
2973    }
2974
2975    /// Applies the confidential transfer pending balance to the available
2976    /// balance
2977    pub async fn confidential_transfer_apply_pending_balance<S: Signers>(
2978        &self,
2979        account: &Address,
2980        authority: &Address,
2981        account_info: Option<ApplyPendingBalanceAccountInfo>,
2982        elgamal_secret_key: &ElGamalSecretKey,
2983        aes_key: &AeKey,
2984        signing_keypairs: &S,
2985    ) -> TokenResult<T::Output> {
2986        let signing_pubkeys = signing_keypairs.pubkeys();
2987        let multisig_signers = self.get_multisig_signers(authority, &signing_pubkeys);
2988
2989        let account_info = if let Some(account_info) = account_info {
2990            account_info
2991        } else {
2992            let account = self.get_account_info(account).await?;
2993            let confidential_transfer_account =
2994                account.get_extension::<ConfidentialTransferAccount>()?;
2995            ApplyPendingBalanceAccountInfo::new(confidential_transfer_account)
2996        };
2997
2998        let expected_pending_balance_credit_counter = account_info.pending_balance_credit_counter();
2999        let new_decryptable_available_balance = account_info
3000            .new_decryptable_available_balance(elgamal_secret_key, aes_key)
3001            .map_err(|_| TokenError::AccountDecryption)?
3002            .into();
3003
3004        self.process_ixs(
3005            &[confidential_transfer::instruction::apply_pending_balance(
3006                &self.program_id,
3007                account,
3008                expected_pending_balance_credit_counter,
3009                &new_decryptable_available_balance,
3010                authority,
3011                &multisig_signers,
3012            )?],
3013            signing_keypairs,
3014        )
3015        .await
3016    }
3017
3018    /// Enable confidential transfer `Deposit` and `Transfer` instructions for a
3019    /// token account
3020    pub async fn confidential_transfer_enable_confidential_credits<S: Signers>(
3021        &self,
3022        account: &Address,
3023        authority: &Address,
3024        signing_keypairs: &S,
3025    ) -> TokenResult<T::Output> {
3026        let signing_pubkeys = signing_keypairs.pubkeys();
3027        let multisig_signers = self.get_multisig_signers(authority, &signing_pubkeys);
3028
3029        self.process_ixs(
3030            &[
3031                confidential_transfer::instruction::enable_confidential_credits(
3032                    &self.program_id,
3033                    account,
3034                    authority,
3035                    &multisig_signers,
3036                )?,
3037            ],
3038            signing_keypairs,
3039        )
3040        .await
3041    }
3042
3043    /// Disable confidential transfer `Deposit` and `Transfer` instructions for
3044    /// a token account
3045    pub async fn confidential_transfer_disable_confidential_credits<S: Signers>(
3046        &self,
3047        account: &Address,
3048        authority: &Address,
3049        signing_keypairs: &S,
3050    ) -> TokenResult<T::Output> {
3051        let signing_pubkeys = signing_keypairs.pubkeys();
3052        let multisig_signers = self.get_multisig_signers(authority, &signing_pubkeys);
3053
3054        self.process_ixs(
3055            &[
3056                confidential_transfer::instruction::disable_confidential_credits(
3057                    &self.program_id,
3058                    account,
3059                    authority,
3060                    &multisig_signers,
3061                )?,
3062            ],
3063            signing_keypairs,
3064        )
3065        .await
3066    }
3067
3068    /// Enable a confidential extension token account to receive
3069    /// non-confidential payments
3070    pub async fn confidential_transfer_enable_non_confidential_credits<S: Signers>(
3071        &self,
3072        account: &Address,
3073        authority: &Address,
3074        signing_keypairs: &S,
3075    ) -> TokenResult<T::Output> {
3076        let signing_pubkeys = signing_keypairs.pubkeys();
3077        let multisig_signers = self.get_multisig_signers(authority, &signing_pubkeys);
3078
3079        self.process_ixs(
3080            &[
3081                confidential_transfer::instruction::enable_non_confidential_credits(
3082                    &self.program_id,
3083                    account,
3084                    authority,
3085                    &multisig_signers,
3086                )?,
3087            ],
3088            signing_keypairs,
3089        )
3090        .await
3091    }
3092
3093    /// Disable non-confidential payments for a confidential extension token
3094    /// account
3095    pub async fn confidential_transfer_disable_non_confidential_credits<S: Signers>(
3096        &self,
3097        account: &Address,
3098        authority: &Address,
3099        signing_keypairs: &S,
3100    ) -> TokenResult<T::Output> {
3101        let signing_pubkeys = signing_keypairs.pubkeys();
3102        let multisig_signers = self.get_multisig_signers(authority, &signing_pubkeys);
3103
3104        self.process_ixs(
3105            &[
3106                confidential_transfer::instruction::disable_non_confidential_credits(
3107                    &self.program_id,
3108                    account,
3109                    authority,
3110                    &multisig_signers,
3111                )?,
3112            ],
3113            signing_keypairs,
3114        )
3115        .await
3116    }
3117
3118    /// Withdraw withheld confidential tokens from mint
3119    #[allow(clippy::too_many_arguments)]
3120    pub async fn confidential_transfer_withdraw_withheld_tokens_from_mint<S: Signers>(
3121        &self,
3122        destination_account: &Address,
3123        withdraw_withheld_authority: &Address,
3124        context_state_account: Option<&Address>,
3125        withheld_tokens_info: Option<WithheldTokensInfo>,
3126        withdraw_withheld_authority_elgamal_keypair: &ElGamalKeypair,
3127        destination_elgamal_pubkey: &ElGamalPubkey,
3128        new_decryptable_available_balance: &DecryptableBalance,
3129        signing_keypairs: &S,
3130    ) -> TokenResult<T::Output> {
3131        let signing_pubkeys = signing_keypairs.pubkeys();
3132        let multisig_signers =
3133            self.get_multisig_signers(withdraw_withheld_authority, &signing_pubkeys);
3134
3135        let account_info = if let Some(account_info) = withheld_tokens_info {
3136            account_info
3137        } else {
3138            let mint_info = self.get_mint_info().await?;
3139            let confidential_transfer_fee_config =
3140                mint_info.get_extension::<ConfidentialTransferFeeConfig>()?;
3141            WithheldTokensInfo::new(&confidential_transfer_fee_config.withheld_amount)
3142        };
3143
3144        let proof_data = if context_state_account.is_some() {
3145            None
3146        } else {
3147            Some(
3148                account_info
3149                    .generate_proof_data(
3150                        withdraw_withheld_authority_elgamal_keypair,
3151                        destination_elgamal_pubkey,
3152                    )
3153                    .map_err(|_| TokenError::ProofGeneration)?,
3154            )
3155        };
3156
3157        // cannot panic as long as either `proof_data` or `context_state_account` is `Some(..)`,
3158        // which is guaranteed by the previous check
3159        let proof_location = Self::confidential_transfer_create_proof_location(
3160            proof_data.as_ref(),
3161            context_state_account,
3162            1,
3163        )
3164        .unwrap();
3165
3166        self.process_ixs(
3167            &confidential_transfer_fee::instruction::withdraw_withheld_tokens_from_mint(
3168                &self.program_id,
3169                &self.pubkey,
3170                destination_account,
3171                new_decryptable_available_balance,
3172                withdraw_withheld_authority,
3173                &multisig_signers,
3174                proof_location,
3175            )?,
3176            signing_keypairs,
3177        )
3178        .await
3179    }
3180
3181    /// Withdraw withheld confidential tokens from accounts
3182    #[allow(clippy::too_many_arguments)]
3183    pub async fn confidential_transfer_withdraw_withheld_tokens_from_accounts<S: Signers>(
3184        &self,
3185        destination_account: &Address,
3186        withdraw_withheld_authority: &Address,
3187        context_state_account: Option<&Address>,
3188        withheld_tokens_info: Option<WithheldTokensInfo>,
3189        withdraw_withheld_authority_elgamal_keypair: &ElGamalKeypair,
3190        destination_elgamal_pubkey: &ElGamalPubkey,
3191        new_decryptable_available_balance: &DecryptableBalance,
3192        sources: &[&Address],
3193        signing_keypairs: &S,
3194    ) -> TokenResult<T::Output> {
3195        let signing_pubkeys = signing_keypairs.pubkeys();
3196        let multisig_signers =
3197            self.get_multisig_signers(withdraw_withheld_authority, &signing_pubkeys);
3198
3199        let account_info = if let Some(account_info) = withheld_tokens_info {
3200            account_info
3201        } else {
3202            let futures = sources.iter().map(|source| self.get_account_info(source));
3203            let sources_extensions = join_all(futures).await;
3204
3205            let mut aggregate_withheld_amount = ElGamalCiphertext::default();
3206            for source_extension in sources_extensions {
3207                let withheld_amount: ElGamalCiphertext = source_extension?
3208                    .get_extension::<ConfidentialTransferFeeAmount>()?
3209                    .withheld_amount
3210                    .try_into()
3211                    .map_err(|_| TokenError::AccountDecryption)?;
3212                aggregate_withheld_amount = aggregate_withheld_amount + withheld_amount;
3213            }
3214
3215            WithheldTokensInfo::new(&aggregate_withheld_amount.into())
3216        };
3217
3218        let proof_data = if context_state_account.is_some() {
3219            None
3220        } else {
3221            Some(
3222                account_info
3223                    .generate_proof_data(
3224                        withdraw_withheld_authority_elgamal_keypair,
3225                        destination_elgamal_pubkey,
3226                    )
3227                    .map_err(|_| TokenError::ProofGeneration)?,
3228            )
3229        };
3230
3231        // cannot panic as long as either `proof_data` or `proof_account` is `Some(..)`,
3232        // which is guaranteed by the previous check
3233        let proof_location = Self::confidential_transfer_create_proof_location(
3234            proof_data.as_ref(),
3235            context_state_account,
3236            1,
3237        )
3238        .unwrap();
3239
3240        self.process_ixs(
3241            &confidential_transfer_fee::instruction::withdraw_withheld_tokens_from_accounts(
3242                &self.program_id,
3243                &self.pubkey,
3244                destination_account,
3245                new_decryptable_available_balance,
3246                withdraw_withheld_authority,
3247                &multisig_signers,
3248                sources,
3249                proof_location,
3250            )?,
3251            signing_keypairs,
3252        )
3253        .await
3254    }
3255
3256    /// Harvest withheld confidential tokens to mint
3257    pub async fn confidential_transfer_harvest_withheld_tokens_to_mint(
3258        &self,
3259        sources: &[&Address],
3260    ) -> TokenResult<T::Output> {
3261        self.process_ixs::<[&dyn Signer; 0]>(
3262            &[
3263                confidential_transfer_fee::instruction::harvest_withheld_tokens_to_mint(
3264                    &self.program_id,
3265                    &self.pubkey,
3266                    sources,
3267                )?,
3268            ],
3269            &[],
3270        )
3271        .await
3272    }
3273
3274    /// Enable harvest of confidential fees to mint
3275    pub async fn confidential_transfer_enable_harvest_to_mint<S: Signers>(
3276        &self,
3277        withdraw_withheld_authority: &Address,
3278        signing_keypairs: &S,
3279    ) -> TokenResult<T::Output> {
3280        let signing_pubkeys = signing_keypairs.pubkeys();
3281        let multisig_signers =
3282            self.get_multisig_signers(withdraw_withheld_authority, &signing_pubkeys);
3283
3284        self.process_ixs(
3285            &[
3286                confidential_transfer_fee::instruction::enable_harvest_to_mint(
3287                    &self.program_id,
3288                    &self.pubkey,
3289                    withdraw_withheld_authority,
3290                    &multisig_signers,
3291                )?,
3292            ],
3293            signing_keypairs,
3294        )
3295        .await
3296    }
3297
3298    /// Disable harvest of confidential fees to mint
3299    pub async fn confidential_transfer_disable_harvest_to_mint<S: Signers>(
3300        &self,
3301        withdraw_withheld_authority: &Address,
3302        signing_keypairs: &S,
3303    ) -> TokenResult<T::Output> {
3304        let signing_pubkeys = signing_keypairs.pubkeys();
3305        let multisig_signers =
3306            self.get_multisig_signers(withdraw_withheld_authority, &signing_pubkeys);
3307
3308        self.process_ixs(
3309            &[
3310                confidential_transfer_fee::instruction::disable_harvest_to_mint(
3311                    &self.program_id,
3312                    &self.pubkey,
3313                    withdraw_withheld_authority,
3314                    &multisig_signers,
3315                )?,
3316            ],
3317            signing_keypairs,
3318        )
3319        .await
3320    }
3321
3322    /// Rotate supply ElGamal public key in a confidential mint
3323    #[allow(clippy::too_many_arguments)]
3324    pub async fn confidential_transfer_rotate_supply_elgamal_pubkey<S: Signers>(
3325        &self,
3326        authority: &Address,
3327        current_supply_elgamal_keypair: &ElGamalKeypair,
3328        new_supply_elgamal_pubkey: &ElGamalPubkey,
3329        aes_key: &AeKey,
3330        context_state_account: Option<&Address>,
3331        account_info: Option<SupplyAccountInfo>,
3332        signing_keypairs: &S,
3333    ) -> TokenResult<T::Output> {
3334        let signing_pubkeys = signing_keypairs.pubkeys();
3335        let multisig_signers = self.get_multisig_signers(authority, &signing_pubkeys);
3336
3337        let account_info = if let Some(account_info) = account_info {
3338            account_info
3339        } else {
3340            let account = self.get_mint_info().await?;
3341            let confidential_supply_account = account.get_extension::<ConfidentialMintBurn>()?;
3342            SupplyAccountInfo::new(confidential_supply_account)
3343        };
3344
3345        let proof_data = if context_state_account.is_some() {
3346            None
3347        } else {
3348            Some(
3349                account_info
3350                    .generate_rotate_supply_elgamal_pubkey_proof(
3351                        current_supply_elgamal_keypair,
3352                        new_supply_elgamal_pubkey,
3353                        aes_key,
3354                    )
3355                    .map_err(|_| TokenError::ProofGeneration)?,
3356            )
3357        };
3358
3359        // cannot panic as long as either `proof_data` or `proof_account` is `Some(..)`,
3360        // which is guaranteed by the previous check
3361        let proof_location = Self::confidential_transfer_create_proof_location(
3362            proof_data.as_ref(),
3363            context_state_account,
3364            1,
3365        )
3366        .unwrap();
3367
3368        let new_supply_elgamal_pubkey = (*new_supply_elgamal_pubkey).into();
3369        self.process_ixs(
3370            &confidential_mint_burn::instruction::rotate_supply_elgamal_pubkey(
3371                &self.program_id,
3372                &self.pubkey,
3373                authority,
3374                &multisig_signers,
3375                &new_supply_elgamal_pubkey,
3376                proof_location,
3377            )?,
3378            signing_keypairs,
3379        )
3380        .await
3381    }
3382
3383    /// Update decryptable supply in a confidential mint
3384    pub async fn confidential_transfer_update_decrypt_supply<S: Signers>(
3385        &self,
3386        authority: &Address,
3387        new_decryptable_supply: &DecryptableBalance,
3388        signing_keypairs: &S,
3389    ) -> TokenResult<T::Output> {
3390        let signing_pubkeys = signing_keypairs.pubkeys();
3391        let multisig_signers = self.get_multisig_signers(authority, &signing_pubkeys);
3392
3393        self.process_ixs(
3394            &[
3395                confidential_mint_burn::instruction::update_decryptable_supply(
3396                    &self.program_id,
3397                    &self.pubkey,
3398                    authority,
3399                    &multisig_signers,
3400                    new_decryptable_supply,
3401                )?,
3402            ],
3403            signing_keypairs,
3404        )
3405        .await
3406    }
3407
3408    /// Confidentially mint tokens
3409    #[allow(clippy::too_many_arguments)]
3410    pub async fn confidential_transfer_mint<S: Signers>(
3411        &self,
3412        authority: &Address,
3413        destination_account: &Address,
3414        equality_proof_account: Option<&Address>,
3415        ciphertext_validity_proof_account_with_ciphertext: Option<&ProofAccountWithCiphertext>,
3416        range_proof_account: Option<&Address>,
3417        mint_amount: u64,
3418        supply_elgamal_keypair: &ElGamalKeypair,
3419        destination_elgamal_pubkey: &ElGamalPubkey,
3420        auditor_elgamal_pubkey: Option<&ElGamalPubkey>,
3421        aes_key: &AeKey,
3422        account_info: Option<SupplyAccountInfo>,
3423        signing_keypairs: &S,
3424    ) -> TokenResult<T::Output> {
3425        let signing_pubkeys = signing_keypairs.pubkeys();
3426        let multisig_signers = self.get_multisig_signers(authority, &signing_pubkeys);
3427
3428        let account_info = if let Some(account_info) = account_info {
3429            account_info
3430        } else {
3431            let account = self.get_mint_info().await?;
3432            let confidential_supply_account = account.get_extension::<ConfidentialMintBurn>()?;
3433            SupplyAccountInfo::new(confidential_supply_account)
3434        };
3435
3436        let (equality_proof_data, ciphertext_validity_proof_data_with_ciphertext, range_proof_data) =
3437            if equality_proof_account.is_some()
3438                && ciphertext_validity_proof_account_with_ciphertext.is_some()
3439                && range_proof_account.is_some()
3440            {
3441                // if all proofs come from accounts, then skip proof generation
3442                (None, None, None)
3443            } else {
3444                let MintProofData {
3445                    equality_proof_data,
3446                    ciphertext_validity_proof_data_with_ciphertext,
3447                    range_proof_data,
3448                } = account_info
3449                    .generate_split_mint_proof_data(
3450                        mint_amount,
3451                        supply_elgamal_keypair,
3452                        aes_key,
3453                        destination_elgamal_pubkey,
3454                        auditor_elgamal_pubkey,
3455                    )
3456                    .map_err(|_| TokenError::ProofGeneration)?;
3457
3458                let equality_proof_data = equality_proof_account
3459                    .is_none()
3460                    .then_some(equality_proof_data);
3461                let ciphertext_validity_proof_data_with_ciphertext =
3462                    ciphertext_validity_proof_account_with_ciphertext
3463                        .is_none()
3464                        .then_some(ciphertext_validity_proof_data_with_ciphertext);
3465                let range_proof_data = range_proof_account.is_none().then_some(range_proof_data);
3466
3467                (
3468                    equality_proof_data,
3469                    ciphertext_validity_proof_data_with_ciphertext,
3470                    range_proof_data,
3471                )
3472            };
3473
3474        // cannot panic as long as either `proof_data` or `proof_account` is `Some(..)`,
3475        // which is guaranteed by the previous check
3476        let equality_proof_location = Self::confidential_transfer_create_proof_location(
3477            equality_proof_data.as_ref(),
3478            equality_proof_account,
3479            1,
3480        )
3481        .unwrap();
3482        let ciphertext_validity_proof_data =
3483            ciphertext_validity_proof_data_with_ciphertext.map(|data| data.proof_data);
3484        let ciphertext_validity_proof_location = Self::confidential_transfer_create_proof_location(
3485            ciphertext_validity_proof_data.as_ref(),
3486            ciphertext_validity_proof_account_with_ciphertext
3487                .map(|account| &account.context_state_account),
3488            2,
3489        )
3490        .unwrap();
3491        let range_proof_location = Self::confidential_transfer_create_proof_location(
3492            range_proof_data.as_ref(),
3493            range_proof_account,
3494            3,
3495        )
3496        .unwrap();
3497
3498        let (mint_amount_auditor_ciphertext_lo, mint_amount_auditor_ciphertext_hi) = if let Some(
3499            proof_data_with_ciphertext,
3500        ) =
3501            ciphertext_validity_proof_data_with_ciphertext
3502        {
3503            (
3504                proof_data_with_ciphertext.ciphertext_lo,
3505                proof_data_with_ciphertext.ciphertext_hi,
3506            )
3507        } else {
3508            // unwrap is safe as long as either `proof_data_with_ciphertext`,
3509            // `proof_account_with_ciphertext` is `Some(..)`, which is guaranteed by the
3510            // previous check
3511            (
3512                ciphertext_validity_proof_account_with_ciphertext
3513                    .unwrap()
3514                    .ciphertext_lo,
3515                ciphertext_validity_proof_account_with_ciphertext
3516                    .unwrap()
3517                    .ciphertext_hi,
3518            )
3519        };
3520
3521        let new_decryptable_supply = account_info
3522            .new_decryptable_supply(mint_amount, supply_elgamal_keypair, aes_key)
3523            .map_err(|_| TokenError::AccountDecryption)?
3524            .into();
3525
3526        self.process_ixs(
3527            &confidential_mint_burn::instruction::confidential_mint_with_split_proofs(
3528                &self.program_id,
3529                destination_account,
3530                &self.pubkey,
3531                &mint_amount_auditor_ciphertext_lo,
3532                &mint_amount_auditor_ciphertext_hi,
3533                authority,
3534                &multisig_signers,
3535                equality_proof_location,
3536                ciphertext_validity_proof_location,
3537                range_proof_location,
3538                &new_decryptable_supply,
3539            )?,
3540            signing_keypairs,
3541        )
3542        .await
3543    }
3544
3545    /// Confidentially burn tokens with permissioned burn authority
3546    #[allow(clippy::too_many_arguments)]
3547    pub async fn confidential_transfer_permissioned_burn<S: Signers>(
3548        &self,
3549        authority: &Address,
3550        source_account: &Address,
3551        permissioned_burn_authority: &Address,
3552        equality_proof_account: Option<&Address>,
3553        ciphertext_validity_proof_account_with_ciphertext: Option<&ProofAccountWithCiphertext>,
3554        range_proof_account: Option<&Address>,
3555        burn_amount: u64,
3556        source_elgamal_keypair: &ElGamalKeypair,
3557        supply_elgamal_pubkey: &ElGamalPubkey,
3558        auditor_elgamal_pubkey: Option<&ElGamalPubkey>,
3559        aes_key: &AeKey,
3560        account_info: Option<BurnAccountInfo>,
3561        signing_keypairs: &S,
3562    ) -> TokenResult<T::Output> {
3563        self.confidential_transfer_burn_inner(
3564            authority,
3565            source_account,
3566            Some(permissioned_burn_authority),
3567            equality_proof_account,
3568            ciphertext_validity_proof_account_with_ciphertext,
3569            range_proof_account,
3570            burn_amount,
3571            source_elgamal_keypair,
3572            supply_elgamal_pubkey,
3573            auditor_elgamal_pubkey,
3574            aes_key,
3575            account_info,
3576            signing_keypairs,
3577        )
3578        .await
3579    }
3580
3581    /// Confidentially burn tokens
3582    #[allow(clippy::too_many_arguments)]
3583    pub async fn confidential_transfer_burn<S: Signers>(
3584        &self,
3585        authority: &Address,
3586        source_account: &Address,
3587        equality_proof_account: Option<&Address>,
3588        ciphertext_validity_proof_account_with_ciphertext: Option<&ProofAccountWithCiphertext>,
3589        range_proof_account: Option<&Address>,
3590        burn_amount: u64,
3591        source_elgamal_keypair: &ElGamalKeypair,
3592        supply_elgamal_pubkey: &ElGamalPubkey,
3593        auditor_elgamal_pubkey: Option<&ElGamalPubkey>,
3594        aes_key: &AeKey,
3595        account_info: Option<BurnAccountInfo>,
3596        signing_keypairs: &S,
3597    ) -> TokenResult<T::Output> {
3598        self.confidential_transfer_burn_inner(
3599            authority,
3600            source_account,
3601            None,
3602            equality_proof_account,
3603            ciphertext_validity_proof_account_with_ciphertext,
3604            range_proof_account,
3605            burn_amount,
3606            source_elgamal_keypair,
3607            supply_elgamal_pubkey,
3608            auditor_elgamal_pubkey,
3609            aes_key,
3610            account_info,
3611            signing_keypairs,
3612        )
3613        .await
3614    }
3615
3616    /// Confidentially burn tokens
3617    #[allow(clippy::too_many_arguments)]
3618    async fn confidential_transfer_burn_inner<S: Signers>(
3619        &self,
3620        authority: &Address,
3621        source_account: &Address,
3622        maybe_permissioned_burn_authority: Option<&Address>,
3623        equality_proof_account: Option<&Address>,
3624        ciphertext_validity_proof_account_with_ciphertext: Option<&ProofAccountWithCiphertext>,
3625        range_proof_account: Option<&Address>,
3626        burn_amount: u64,
3627        source_elgamal_keypair: &ElGamalKeypair,
3628        supply_elgamal_pubkey: &ElGamalPubkey,
3629        auditor_elgamal_pubkey: Option<&ElGamalPubkey>,
3630        aes_key: &AeKey,
3631        account_info: Option<BurnAccountInfo>,
3632        signing_keypairs: &S,
3633    ) -> TokenResult<T::Output> {
3634        let signing_pubkeys = signing_keypairs.pubkeys();
3635        let multisig_signers = self.get_multisig_signers(authority, &signing_pubkeys);
3636
3637        let account_info = if let Some(account_info) = account_info {
3638            account_info
3639        } else {
3640            let account = self.get_account_info(source_account).await?;
3641            let confidential_supply_account =
3642                account.get_extension::<ConfidentialTransferAccount>()?;
3643            BurnAccountInfo::new(confidential_supply_account)
3644        };
3645
3646        let (equality_proof_data, ciphertext_validity_proof_data_with_ciphertext, range_proof_data) =
3647            if equality_proof_account.is_some()
3648                && ciphertext_validity_proof_account_with_ciphertext.is_some()
3649                && range_proof_account.is_some()
3650            {
3651                // if all proofs come from accounts, then skip proof generation
3652                (None, None, None)
3653            } else {
3654                let BurnProofData {
3655                    equality_proof_data,
3656                    ciphertext_validity_proof_data_with_ciphertext,
3657                    range_proof_data,
3658                } = account_info
3659                    .generate_split_burn_proof_data(
3660                        burn_amount,
3661                        source_elgamal_keypair,
3662                        aes_key,
3663                        supply_elgamal_pubkey,
3664                        auditor_elgamal_pubkey,
3665                    )
3666                    .map_err(|_| TokenError::ProofGeneration)?;
3667
3668                let equality_proof_data = equality_proof_account
3669                    .is_none()
3670                    .then_some(equality_proof_data);
3671                let ciphertext_validity_proof_data_with_ciphertext =
3672                    ciphertext_validity_proof_account_with_ciphertext
3673                        .is_none()
3674                        .then_some(ciphertext_validity_proof_data_with_ciphertext);
3675                let range_proof_data = range_proof_account.is_none().then_some(range_proof_data);
3676
3677                (
3678                    equality_proof_data,
3679                    ciphertext_validity_proof_data_with_ciphertext,
3680                    range_proof_data,
3681                )
3682            };
3683
3684        // cannot panic as long as either `proof_data` or `proof_account` is `Some(..)`,
3685        // which is guaranteed by the previous check
3686        let equality_proof_location = Self::confidential_transfer_create_proof_location(
3687            equality_proof_data.as_ref(),
3688            equality_proof_account,
3689            1,
3690        )
3691        .unwrap();
3692        let ciphertext_validity_proof_data =
3693            ciphertext_validity_proof_data_with_ciphertext.map(|data| data.proof_data);
3694        let ciphertext_validity_proof_location = Self::confidential_transfer_create_proof_location(
3695            ciphertext_validity_proof_data.as_ref(),
3696            ciphertext_validity_proof_account_with_ciphertext
3697                .map(|account| &account.context_state_account),
3698            2,
3699        )
3700        .unwrap();
3701        let range_proof_location = Self::confidential_transfer_create_proof_location(
3702            range_proof_data.as_ref(),
3703            range_proof_account,
3704            3,
3705        )
3706        .unwrap();
3707
3708        let (burn_amount_auditor_ciphertext_lo, burn_amount_auditor_ciphertext_hi) = if let Some(
3709            proof_data_with_ciphertext,
3710        ) =
3711            ciphertext_validity_proof_data_with_ciphertext
3712        {
3713            (
3714                proof_data_with_ciphertext.ciphertext_lo,
3715                proof_data_with_ciphertext.ciphertext_hi,
3716            )
3717        } else {
3718            // unwrap is safe as long as either `proof_data_with_ciphertext`,
3719            // `proof_account_with_ciphertext` is `Some(..)`, which is guaranteed by the
3720            // previous check
3721            (
3722                ciphertext_validity_proof_account_with_ciphertext
3723                    .unwrap()
3724                    .ciphertext_lo,
3725                ciphertext_validity_proof_account_with_ciphertext
3726                    .unwrap()
3727                    .ciphertext_hi,
3728            )
3729        };
3730
3731        let new_decryptable_balance = account_info
3732            .new_decryptable_balance(burn_amount, aes_key)
3733            .map_err(|_| TokenError::AccountDecryption)?
3734            .into();
3735
3736        let instructions =
3737            if let Some(permissioned_burn_authority) = maybe_permissioned_burn_authority {
3738                permissioned_burn::instruction::confidential_burn_with_split_proofs(
3739                    &self.program_id,
3740                    source_account,
3741                    &self.pubkey,
3742                    permissioned_burn_authority,
3743                    &new_decryptable_balance,
3744                    &burn_amount_auditor_ciphertext_lo,
3745                    &burn_amount_auditor_ciphertext_hi,
3746                    authority,
3747                    &multisig_signers,
3748                    equality_proof_location,
3749                    ciphertext_validity_proof_location,
3750                    range_proof_location,
3751                )?
3752            } else {
3753                confidential_mint_burn::instruction::confidential_burn_with_split_proofs(
3754                    &self.program_id,
3755                    source_account,
3756                    &self.pubkey,
3757                    &new_decryptable_balance,
3758                    &burn_amount_auditor_ciphertext_lo,
3759                    &burn_amount_auditor_ciphertext_hi,
3760                    authority,
3761                    &multisig_signers,
3762                    equality_proof_location,
3763                    ciphertext_validity_proof_location,
3764                    range_proof_location,
3765                )?
3766            };
3767        self.process_ixs(&instructions, signing_keypairs).await
3768    }
3769
3770    /// Apply pending burn amount to the confidential supply amount
3771    pub async fn confidential_transfer_apply_pending_burn<S: Signers>(
3772        &self,
3773        authority: &Address,
3774        signing_keypairs: &S,
3775    ) -> TokenResult<T::Output> {
3776        let signing_pubkeys = signing_keypairs.pubkeys();
3777        let multisig_signers = self.get_multisig_signers(authority, &signing_pubkeys);
3778
3779        self.process_ixs(
3780            &[confidential_mint_burn::instruction::apply_pending_burn(
3781                &self.program_id,
3782                &self.pubkey,
3783                authority,
3784                &multisig_signers,
3785            )?],
3786            signing_keypairs,
3787        )
3788        .await
3789    }
3790
3791    // Creates `ProofLocation` from proof data and context account. If both
3792    // `proof_data` and `context_account` are `None`, then the result is `None`.
3793    fn confidential_transfer_create_proof_location<'a, ZK: ZkProofData<U>, U: Pod>(
3794        proof_data: Option<&'a ZK>,
3795        context_account: Option<&'a Address>,
3796        instruction_offset: i8,
3797    ) -> Option<ProofLocation<'a, ZK>> {
3798        if let Some(proof_data) = proof_data {
3799            Some(ProofLocation::InstructionOffset(
3800                instruction_offset.try_into().unwrap(),
3801                proof_data,
3802            ))
3803        } else {
3804            context_account.map(ProofLocation::ContextStateAccount)
3805        }
3806    }
3807
3808    pub async fn withdraw_excess_lamports<S: Signers>(
3809        &self,
3810        source: &Address,
3811        destination: &Address,
3812        authority: &Address,
3813        signing_keypairs: &S,
3814    ) -> TokenResult<T::Output> {
3815        let signing_pubkeys = signing_keypairs.pubkeys();
3816        let multisig_signers = self.get_multisig_signers(authority, &signing_pubkeys);
3817
3818        self.process_ixs(
3819            &[
3820                spl_token_2022_interface::instruction::withdraw_excess_lamports(
3821                    &self.program_id,
3822                    source,
3823                    destination,
3824                    authority,
3825                    &multisig_signers,
3826                )?,
3827            ],
3828            signing_keypairs,
3829        )
3830        .await
3831    }
3832
3833    /// Initialize token-metadata on a mint
3834    pub async fn token_metadata_initialize<S: Signers>(
3835        &self,
3836        update_authority: &Address,
3837        mint_authority: &Address,
3838        name: String,
3839        symbol: String,
3840        uri: String,
3841        signing_keypairs: &S,
3842    ) -> TokenResult<T::Output> {
3843        self.process_ixs(
3844            &[spl_token_metadata_interface::instruction::initialize(
3845                &self.program_id,
3846                &self.pubkey,
3847                update_authority,
3848                &self.pubkey,
3849                mint_authority,
3850                name,
3851                symbol,
3852                uri,
3853            )],
3854            signing_keypairs,
3855        )
3856        .await
3857    }
3858
3859    async fn get_additional_rent_for_new_metadata(
3860        &self,
3861        token_metadata: &TokenMetadata,
3862    ) -> TokenResult<u64> {
3863        let account = self.get_account(self.pubkey).await?;
3864        let account_lamports = account.lamports;
3865        let mint_state = self.unpack_mint_info(account)?;
3866        let new_account_len = mint_state
3867            .try_get_new_account_len_for_variable_len_extension::<TokenMetadata>(token_metadata)?;
3868        let new_rent_exempt_minimum = self
3869            .client
3870            .get_minimum_balance_for_rent_exemption(new_account_len)
3871            .await
3872            .map_err(TokenError::Client)?;
3873        Ok(new_rent_exempt_minimum.saturating_sub(account_lamports))
3874    }
3875
3876    /// Initialize token-metadata on a mint
3877    #[allow(clippy::too_many_arguments)]
3878    pub async fn token_metadata_initialize_with_rent_transfer<S: Signers>(
3879        &self,
3880        payer: &Address,
3881        update_authority: &Address,
3882        mint_authority: &Address,
3883        name: String,
3884        symbol: String,
3885        uri: String,
3886        signing_keypairs: &S,
3887    ) -> TokenResult<T::Output> {
3888        let token_metadata = TokenMetadata {
3889            name,
3890            symbol,
3891            uri,
3892            ..Default::default()
3893        };
3894        let additional_lamports = self
3895            .get_additional_rent_for_new_metadata(&token_metadata)
3896            .await?;
3897        let mut instructions = vec![];
3898        if additional_lamports > 0 {
3899            instructions.push(system_instruction::transfer(
3900                payer,
3901                &self.pubkey,
3902                additional_lamports,
3903            ));
3904        }
3905        instructions.push(spl_token_metadata_interface::instruction::initialize(
3906            &self.program_id,
3907            &self.pubkey,
3908            update_authority,
3909            &self.pubkey,
3910            mint_authority,
3911            token_metadata.name,
3912            token_metadata.symbol,
3913            token_metadata.uri,
3914        ));
3915        self.process_ixs(&instructions, signing_keypairs).await
3916    }
3917
3918    /// Update a token-metadata field on a mint
3919    pub async fn token_metadata_update_field<S: Signers>(
3920        &self,
3921        update_authority: &Address,
3922        field: Field,
3923        value: String,
3924        signing_keypairs: &S,
3925    ) -> TokenResult<T::Output> {
3926        self.process_ixs(
3927            &[spl_token_metadata_interface::instruction::update_field(
3928                &self.program_id,
3929                &self.pubkey,
3930                update_authority,
3931                field,
3932                value,
3933            )],
3934            signing_keypairs,
3935        )
3936        .await
3937    }
3938
3939    async fn get_additional_rent_for_updated_metadata(
3940        &self,
3941        field: Field,
3942        value: String,
3943    ) -> TokenResult<u64> {
3944        let account = self.get_account(self.pubkey).await?;
3945        let account_lamports = account.lamports;
3946        let mint_state = self.unpack_mint_info(account)?;
3947        let mut token_metadata = mint_state.get_variable_len_extension::<TokenMetadata>()?;
3948        token_metadata.update(field, value);
3949        let new_account_len = mint_state
3950            .try_get_new_account_len_for_variable_len_extension::<TokenMetadata>(&token_metadata)?;
3951        let new_rent_exempt_minimum = self
3952            .client
3953            .get_minimum_balance_for_rent_exemption(new_account_len)
3954            .await
3955            .map_err(TokenError::Client)?;
3956        Ok(new_rent_exempt_minimum.saturating_sub(account_lamports))
3957    }
3958
3959    /// Update a token-metadata field on a mint. Includes a transfer for any
3960    /// additional rent-exempt SOL required.
3961    #[allow(clippy::too_many_arguments)]
3962    pub async fn token_metadata_update_field_with_rent_transfer<S: Signers>(
3963        &self,
3964        payer: &Address,
3965        update_authority: &Address,
3966        field: Field,
3967        value: String,
3968        transfer_lamports: Option<u64>,
3969        signing_keypairs: &S,
3970    ) -> TokenResult<T::Output> {
3971        let additional_lamports = if let Some(transfer_lamports) = transfer_lamports {
3972            transfer_lamports
3973        } else {
3974            self.get_additional_rent_for_updated_metadata(field.clone(), value.clone())
3975                .await?
3976        };
3977        let mut instructions = vec![];
3978        if additional_lamports > 0 {
3979            instructions.push(system_instruction::transfer(
3980                payer,
3981                &self.pubkey,
3982                additional_lamports,
3983            ));
3984        }
3985        instructions.push(spl_token_metadata_interface::instruction::update_field(
3986            &self.program_id,
3987            &self.pubkey,
3988            update_authority,
3989            field,
3990            value,
3991        ));
3992        self.process_ixs(&instructions, signing_keypairs).await
3993    }
3994
3995    /// Update the token-metadata authority in a mint
3996    pub async fn token_metadata_update_authority<S: Signers>(
3997        &self,
3998        current_authority: &Address,
3999        new_authority: Option<Address>,
4000        signing_keypairs: &S,
4001    ) -> TokenResult<T::Output> {
4002        self.process_ixs(
4003            &[spl_token_metadata_interface::instruction::update_authority(
4004                &self.program_id,
4005                &self.pubkey,
4006                current_authority,
4007                MaybeNull::try_from(new_authority).map_err(|_| ProgramError::InvalidArgument)?,
4008            )],
4009            signing_keypairs,
4010        )
4011        .await
4012    }
4013
4014    /// Remove a token-metadata field on a mint
4015    pub async fn token_metadata_remove_key<S: Signers>(
4016        &self,
4017        update_authority: &Address,
4018        key: String,
4019        idempotent: bool,
4020        signing_keypairs: &S,
4021    ) -> TokenResult<T::Output> {
4022        self.process_ixs(
4023            &[spl_token_metadata_interface::instruction::remove_key(
4024                &self.program_id,
4025                &self.pubkey,
4026                update_authority,
4027                key,
4028                idempotent,
4029            )],
4030            signing_keypairs,
4031        )
4032        .await
4033    }
4034
4035    /// Initialize token-group on a mint
4036    pub async fn token_group_initialize<S: Signers>(
4037        &self,
4038        mint_authority: &Address,
4039        update_authority: &Address,
4040        max_size: u64,
4041        signing_keypairs: &S,
4042    ) -> TokenResult<T::Output> {
4043        self.process_ixs(
4044            &[spl_token_group_interface::instruction::initialize_group(
4045                &self.program_id,
4046                &self.pubkey,
4047                &self.pubkey,
4048                mint_authority,
4049                Some(*update_authority),
4050                max_size,
4051            )],
4052            signing_keypairs,
4053        )
4054        .await
4055    }
4056
4057    async fn get_additional_rent_for_fixed_len_extension<V: Extension + Pod>(
4058        &self,
4059    ) -> TokenResult<u64> {
4060        let account = self.get_account(self.pubkey).await?;
4061        let account_lamports = account.lamports;
4062        let mint_state = self.unpack_mint_info(account)?;
4063        if mint_state.get_extension::<V>().is_ok() {
4064            Ok(0)
4065        } else {
4066            let new_account_len = mint_state.try_get_new_account_len::<V>()?;
4067            let new_rent_exempt_minimum = self
4068                .client
4069                .get_minimum_balance_for_rent_exemption(new_account_len)
4070                .await
4071                .map_err(TokenError::Client)?;
4072            Ok(new_rent_exempt_minimum.saturating_sub(account_lamports))
4073        }
4074    }
4075
4076    /// Initialize token-group on a mint
4077    pub async fn token_group_initialize_with_rent_transfer<S: Signers>(
4078        &self,
4079        payer: &Address,
4080        mint_authority: &Address,
4081        update_authority: &Address,
4082        max_size: u64,
4083        signing_keypairs: &S,
4084    ) -> TokenResult<T::Output> {
4085        let additional_lamports = self
4086            .get_additional_rent_for_fixed_len_extension::<TokenGroup>()
4087            .await?;
4088        let mut instructions = vec![];
4089        if additional_lamports > 0 {
4090            instructions.push(system_instruction::transfer(
4091                payer,
4092                &self.pubkey,
4093                additional_lamports,
4094            ));
4095        }
4096        instructions.push(spl_token_group_interface::instruction::initialize_group(
4097            &self.program_id,
4098            &self.pubkey,
4099            &self.pubkey,
4100            mint_authority,
4101            Some(*update_authority),
4102            max_size,
4103        ));
4104        self.process_ixs(&instructions, signing_keypairs).await
4105    }
4106
4107    /// Update a token-group max size on a mint
4108    pub async fn token_group_update_max_size<S: Signers>(
4109        &self,
4110        update_authority: &Address,
4111        new_max_size: u64,
4112        signing_keypairs: &S,
4113    ) -> TokenResult<T::Output> {
4114        self.process_ixs(
4115            &[
4116                spl_token_group_interface::instruction::update_group_max_size(
4117                    &self.program_id,
4118                    &self.pubkey,
4119                    update_authority,
4120                    new_max_size,
4121                ),
4122            ],
4123            signing_keypairs,
4124        )
4125        .await
4126    }
4127
4128    /// Update the token-group authority in a mint
4129    pub async fn token_group_update_authority<S: Signers>(
4130        &self,
4131        current_authority: &Address,
4132        new_authority: Option<Address>,
4133        signing_keypairs: &S,
4134    ) -> TokenResult<T::Output> {
4135        self.process_ixs(
4136            &[
4137                spl_token_group_interface::instruction::update_group_authority(
4138                    &self.program_id,
4139                    &self.pubkey,
4140                    current_authority,
4141                    new_authority,
4142                ),
4143            ],
4144            signing_keypairs,
4145        )
4146        .await
4147    }
4148
4149    /// Initialize a token-group member on a mint
4150    pub async fn token_group_initialize_member<S: Signers>(
4151        &self,
4152        mint_authority: &Address,
4153        group_mint: &Address,
4154        group_update_authority: &Address,
4155        signing_keypairs: &S,
4156    ) -> TokenResult<T::Output> {
4157        self.process_ixs(
4158            &[spl_token_group_interface::instruction::initialize_member(
4159                &self.program_id,
4160                &self.pubkey,
4161                &self.pubkey,
4162                mint_authority,
4163                group_mint,
4164                group_update_authority,
4165            )],
4166            signing_keypairs,
4167        )
4168        .await
4169    }
4170
4171    /// Initialize a token-group member on a mint
4172    #[allow(clippy::too_many_arguments)]
4173    pub async fn token_group_initialize_member_with_rent_transfer<S: Signers>(
4174        &self,
4175        payer: &Address,
4176        mint_authority: &Address,
4177        group_mint: &Address,
4178        group_update_authority: &Address,
4179        signing_keypairs: &S,
4180    ) -> TokenResult<T::Output> {
4181        let additional_lamports = self
4182            .get_additional_rent_for_fixed_len_extension::<TokenGroupMember>()
4183            .await?;
4184        let mut instructions = vec![];
4185        if additional_lamports > 0 {
4186            instructions.push(system_instruction::transfer(
4187                payer,
4188                &self.pubkey,
4189                additional_lamports,
4190            ));
4191        }
4192        instructions.push(spl_token_group_interface::instruction::initialize_member(
4193            &self.program_id,
4194            &self.pubkey,
4195            &self.pubkey,
4196            mint_authority,
4197            group_mint,
4198            group_update_authority,
4199        ));
4200        self.process_ixs(&instructions, signing_keypairs).await
4201    }
4202
4203    /// Get the pending balance for a confidential transfer account.
4204    ///
4205    /// This decrypts and combines the low 16 bits and high 48 bits of the pending balance
4206    /// into a single u64 value.
4207    pub async fn confidential_transfer_get_pending_balance(
4208        &self,
4209        account: &Address,
4210        elgamal_secret_key: &ElGamalSecretKey,
4211    ) -> TokenResult<u64> {
4212        let account_info = self.get_account_info(account).await?;
4213        let confidential_transfer_account =
4214            account_info.get_extension::<ConfidentialTransferAccount>()?;
4215        let account_info = ApplyPendingBalanceAccountInfo::new(confidential_transfer_account);
4216
4217        account_info
4218            .get_pending_balance(elgamal_secret_key)
4219            .map_err(|_| TokenError::AccountDecryption)
4220    }
4221
4222    /// Get the available balance for a confidential transfer account.
4223    ///
4224    /// This decrypts the decryptable available balance using the provided AES key.
4225    pub async fn confidential_transfer_get_available_balance(
4226        &self,
4227        account: &Address,
4228        aes_key: &AeKey,
4229    ) -> TokenResult<u64> {
4230        let account_info = self.get_account_info(account).await?;
4231        let confidential_transfer_account =
4232            account_info.get_extension::<ConfidentialTransferAccount>()?;
4233        let account_info = ApplyPendingBalanceAccountInfo::new(confidential_transfer_account);
4234
4235        account_info
4236            .get_available_balance(aes_key)
4237            .map_err(|_| TokenError::AccountDecryption)
4238    }
4239
4240    /// Get the total balance (pending and available) for a confidential transfer account.
4241    ///
4242    /// This combines both pending and available balances with overflow protection.
4243    pub async fn confidential_transfer_get_total_balance(
4244        &self,
4245        account: &Address,
4246        elgamal_secret_key: &ElGamalSecretKey,
4247        aes_key: &AeKey,
4248    ) -> TokenResult<u64> {
4249        let account_info = self.get_account_info(account).await?;
4250        let confidential_transfer_account =
4251            account_info.get_extension::<ConfidentialTransferAccount>()?;
4252        let account_info = ApplyPendingBalanceAccountInfo::new(confidential_transfer_account);
4253
4254        account_info
4255            .get_total_balance(elgamal_secret_key, aes_key)
4256            .map_err(|e| match e {
4257                spl_token_2022_interface::error::TokenError::Overflow => {
4258                    TokenError::AccountDecryption
4259                }
4260                _ => TokenError::AccountDecryption,
4261            })
4262    }
4263
4264    /// Check if a confidential transfer account has any pending balance.
4265    ///
4266    /// This checks whether the pending_balance_credit_counter is greater than zero.
4267    pub async fn confidential_transfer_has_pending_balance(
4268        &self,
4269        account: &Address,
4270    ) -> TokenResult<bool> {
4271        let account_info = self.get_account_info(account).await?;
4272        let confidential_transfer_account =
4273            account_info.get_extension::<ConfidentialTransferAccount>()?;
4274        let account_info = ApplyPendingBalanceAccountInfo::new(confidential_transfer_account);
4275
4276        Ok(account_info.has_pending_balance())
4277    }
4278}
4279
4280/// Calculates the maximum chunk size for a zero-knowledge proof record
4281/// instruction to fit inside a single transaction.
4282fn calculate_record_max_chunk_size<F>(
4283    create_record_instructions: F,
4284    first_instruction: bool,
4285) -> usize
4286where
4287    F: Fn(bool, &[u8], u64) -> Vec<Instruction>,
4288{
4289    let ixs = create_record_instructions(first_instruction, &[], 0);
4290    let message = Message::new_with_blockhash(&ixs, Some(&Address::default()), &Hash::default());
4291    let tx_size = bincode::serialized_size(&Transaction {
4292        signatures: vec![Signature::default(); message.header.num_required_signatures as usize],
4293        message,
4294    })
4295    .unwrap() as usize;
4296    PACKET_DATA_SIZE.saturating_sub(tx_size).saturating_sub(1)
4297}