spl_token_cli/
command.rs

1#![allow(clippy::arithmetic_side_effects)]
2use {
3    crate::{
4        bench::*,
5        clap_app::*,
6        config::{Config, MintInfo},
7        encryption_keypair::*,
8        output::*,
9        sort::{sort_and_parse_token_accounts, AccountFilter},
10    },
11    clap::{value_t, value_t_or_exit, ArgMatches},
12    futures::try_join,
13    serde::Serialize,
14    solana_account_decoder::{
15        parse_account_data::SplTokenAdditionalDataV2,
16        parse_token::{get_token_account_mint, parse_token_v3, TokenAccountType, UiAccountState},
17        UiAccountData,
18    },
19    solana_clap_v3_utils::{
20        input_parsers::{pubkey_of_signer, pubkeys_of_multiple_signers, Amount},
21        keypair::signer_from_path,
22    },
23    solana_cli_output::{
24        display::build_balance_message, return_signers_data, CliSignOnlyData, CliSignature,
25        OutputFormat, QuietDisplay, ReturnSignersConfig, VerboseDisplay,
26    },
27    solana_client::rpc_request::TokenAccountsFilter,
28    solana_remote_wallet::remote_wallet::RemoteWalletManager,
29    solana_sdk::{
30        instruction::AccountMeta,
31        program_option::COption,
32        pubkey::Pubkey,
33        signature::{Keypair, Signer},
34    },
35    solana_system_interface::program as system_program,
36    spl_associated_token_account_interface::address::get_associated_token_address_with_program_id,
37    spl_pod::optional_keys::OptionalNonZeroPubkey,
38    spl_token_2022::extension::confidential_transfer::account_info::{
39        ApplyPendingBalanceAccountInfo, TransferAccountInfo, WithdrawAccountInfo,
40    },
41    spl_token_2022_interface::{
42        extension::{
43            confidential_transfer::{ConfidentialTransferAccount, ConfidentialTransferMint},
44            confidential_transfer_fee::ConfidentialTransferFeeConfig,
45            cpi_guard::CpiGuard,
46            default_account_state::DefaultAccountState,
47            group_member_pointer::GroupMemberPointer,
48            group_pointer::GroupPointer,
49            interest_bearing_mint::InterestBearingConfig,
50            memo_transfer::MemoTransfer,
51            metadata_pointer::MetadataPointer,
52            mint_close_authority::MintCloseAuthority,
53            pausable::PausableConfig,
54            permanent_delegate::PermanentDelegate,
55            scaled_ui_amount::ScaledUiAmountConfig,
56            transfer_fee::{TransferFeeAmount, TransferFeeConfig},
57            transfer_hook::TransferHook,
58            BaseStateWithExtensions, ExtensionType, StateWithExtensionsOwned,
59        },
60        solana_zk_sdk::encryption::{
61            auth_encryption::AeKey,
62            elgamal::{self, ElGamalKeypair},
63            pod::elgamal::PodElGamalPubkey,
64        },
65        state::{Account, AccountState, Mint},
66    },
67    spl_token_client::{
68        client::{ProgramRpcClientSendTransaction, RpcClientResponse},
69        token::{
70            ComputeUnitLimit, ExtensionInitializationParams, ProofAccountWithCiphertext, Token,
71        },
72    },
73    spl_token_confidential_transfer_proof_generation::{
74        transfer::TransferProofData, withdraw::WithdrawProofData,
75    },
76    spl_token_group_interface::state::TokenGroup,
77    spl_token_metadata_interface::state::{Field, TokenMetadata},
78    std::{
79        collections::HashMap,
80        fmt::Display,
81        process::exit,
82        rc::Rc,
83        str::FromStr,
84        sync::Arc,
85        time::{SystemTime, UNIX_EPOCH},
86    },
87};
88
89fn print_error_and_exit<T, E: Display>(e: E) -> T {
90    eprintln!("error: {}", e);
91    exit(1)
92}
93
94fn amount_to_raw_amount(amount: Amount, decimals: u8, all_amount: Option<u64>, name: &str) -> u64 {
95    match amount {
96        Amount::Raw(ui_amount) => ui_amount,
97        Amount::Decimal(ui_amount) => spl_token_2022::ui_amount_to_amount(ui_amount, decimals),
98        Amount::All => {
99            if let Some(raw_amount) = all_amount {
100                raw_amount
101            } else {
102                eprintln!("ALL keyword is not allowed for {}", name);
103                exit(1)
104            }
105        }
106    }
107}
108
109type BulkSigners = Vec<Arc<dyn Signer>>;
110pub type CommandResult = Result<String, Error>;
111
112fn push_signer_with_dedup(signer: Arc<dyn Signer>, bulk_signers: &mut BulkSigners) {
113    if !bulk_signers.contains(&signer) {
114        bulk_signers.push(signer);
115    }
116}
117
118fn new_throwaway_signer() -> (Arc<dyn Signer>, Pubkey) {
119    let keypair = Keypair::new();
120    let pubkey = keypair.pubkey();
121    (Arc::new(keypair) as Arc<dyn Signer>, pubkey)
122}
123
124fn get_signer(
125    matches: &ArgMatches,
126    keypair_name: &str,
127    wallet_manager: &mut Option<Rc<RemoteWalletManager>>,
128) -> Option<(Arc<dyn Signer>, Pubkey)> {
129    matches.value_of(keypair_name).map(|path| {
130        let signer = signer_from_path(matches, path, keypair_name, wallet_manager)
131            .unwrap_or_else(print_error_and_exit);
132        let signer_pubkey = signer.pubkey();
133        (Arc::from(signer), signer_pubkey)
134    })
135}
136
137async fn check_wallet_balance(
138    config: &Config<'_>,
139    wallet: &Pubkey,
140    required_balance: u64,
141) -> Result<(), Error> {
142    let balance = config.rpc_client.get_balance(wallet).await?;
143    if balance < required_balance {
144        Err(format!(
145            "Wallet {}, has insufficient balance: {} required, {} available",
146            wallet,
147            build_balance_message(required_balance, false, false),
148            build_balance_message(balance, false, false)
149        )
150        .into())
151    } else {
152        Ok(())
153    }
154}
155
156fn base_token_client(
157    config: &Config<'_>,
158    token_pubkey: &Pubkey,
159    decimals: Option<u8>,
160) -> Result<Token<ProgramRpcClientSendTransaction>, Error> {
161    Ok(Token::new(
162        config.program_client.clone(),
163        &config.program_id,
164        token_pubkey,
165        decimals,
166        config.fee_payer()?.clone(),
167    ))
168}
169
170fn config_token_client(
171    token: Token<ProgramRpcClientSendTransaction>,
172    config: &Config<'_>,
173) -> Result<Token<ProgramRpcClientSendTransaction>, Error> {
174    let token = token.with_compute_unit_limit(config.compute_unit_limit.clone());
175
176    let token = if let Some(compute_unit_price) = config.compute_unit_price {
177        token.with_compute_unit_price(compute_unit_price)
178    } else {
179        token
180    };
181
182    if let (Some(nonce_account), Some(nonce_authority), Some(nonce_blockhash)) = (
183        config.nonce_account,
184        &config.nonce_authority,
185        config.nonce_blockhash,
186    ) {
187        Ok(token.with_nonce(
188            &nonce_account,
189            Arc::clone(nonce_authority),
190            &nonce_blockhash,
191        ))
192    } else {
193        Ok(token)
194    }
195}
196
197fn token_client_from_config(
198    config: &Config<'_>,
199    token_pubkey: &Pubkey,
200    decimals: Option<u8>,
201) -> Result<Token<ProgramRpcClientSendTransaction>, Error> {
202    let token = base_token_client(config, token_pubkey, decimals)?;
203    config_token_client(token, config)
204}
205
206fn native_token_client_from_config(
207    config: &Config<'_>,
208) -> Result<Token<ProgramRpcClientSendTransaction>, Error> {
209    let token = Token::new_native(
210        config.program_client.clone(),
211        &config.program_id,
212        config.fee_payer()?.clone(),
213    );
214
215    let token = token.with_compute_unit_limit(config.compute_unit_limit.clone());
216
217    let token = if let Some(compute_unit_price) = config.compute_unit_price {
218        token.with_compute_unit_price(compute_unit_price)
219    } else {
220        token
221    };
222
223    if let (Some(nonce_account), Some(nonce_authority), Some(nonce_blockhash)) = (
224        config.nonce_account,
225        &config.nonce_authority,
226        config.nonce_blockhash,
227    ) {
228        Ok(token.with_nonce(
229            &nonce_account,
230            Arc::clone(nonce_authority),
231            &nonce_blockhash,
232        ))
233    } else {
234        Ok(token)
235    }
236}
237
238#[derive(strum_macros::Display, Debug)]
239#[strum(serialize_all = "kebab-case")]
240enum Pointer {
241    Metadata,
242    Group,
243    GroupMember,
244}
245
246#[allow(clippy::too_many_arguments)]
247async fn command_create_token(
248    config: &Config<'_>,
249    decimals: u8,
250    token_pubkey: Pubkey,
251    authority: Pubkey,
252    enable_freeze: bool,
253    enable_close: bool,
254    enable_non_transferable: bool,
255    enable_permanent_delegate: bool,
256    memo: Option<String>,
257    metadata_address: Option<Pubkey>,
258    group_address: Option<Pubkey>,
259    member_address: Option<Pubkey>,
260    rate_bps: Option<i16>,
261    default_account_state: Option<AccountState>,
262    transfer_fee: Option<(u16, u64)>,
263    confidential_transfer_auto_approve: Option<bool>,
264    transfer_hook_program_id: Option<Pubkey>,
265    enable_metadata: bool,
266    enable_group: bool,
267    enable_member: bool,
268    enable_transfer_hook: bool,
269    ui_multiplier: Option<f64>,
270    pausable: bool,
271    bulk_signers: Vec<Arc<dyn Signer>>,
272) -> CommandResult {
273    println_display(
274        config,
275        format!(
276            "Creating token {} under program {}",
277            token_pubkey, config.program_id
278        ),
279    );
280
281    let token = token_client_from_config(config, &token_pubkey, Some(decimals))?;
282
283    let freeze_authority = if enable_freeze { Some(authority) } else { None };
284
285    let mut extensions = vec![];
286
287    if enable_close {
288        extensions.push(ExtensionInitializationParams::MintCloseAuthority {
289            close_authority: Some(authority),
290        });
291    }
292
293    if enable_permanent_delegate {
294        extensions.push(ExtensionInitializationParams::PermanentDelegate {
295            delegate: authority,
296        });
297    }
298
299    if let Some(rate_bps) = rate_bps {
300        extensions.push(ExtensionInitializationParams::InterestBearingConfig {
301            rate_authority: Some(authority),
302            rate: rate_bps,
303        })
304    }
305
306    if enable_non_transferable {
307        extensions.push(ExtensionInitializationParams::NonTransferable);
308    }
309
310    if let Some(state) = default_account_state {
311        assert!(
312            enable_freeze,
313            "Token requires a freeze authority to default to frozen accounts"
314        );
315        extensions.push(ExtensionInitializationParams::DefaultAccountState { state })
316    }
317
318    if let Some((transfer_fee_basis_points, maximum_fee)) = transfer_fee {
319        extensions.push(ExtensionInitializationParams::TransferFeeConfig {
320            transfer_fee_config_authority: Some(authority),
321            withdraw_withheld_authority: Some(authority),
322            transfer_fee_basis_points,
323            maximum_fee,
324        });
325    }
326
327    if let Some(auto_approve) = confidential_transfer_auto_approve {
328        extensions.push(ExtensionInitializationParams::ConfidentialTransferMint {
329            authority: Some(authority),
330            auto_approve_new_accounts: auto_approve,
331            auditor_elgamal_pubkey: None,
332        });
333        if transfer_fee.is_some() {
334            // Deriving ElGamal key from default signer. Custom ElGamal keys
335            // will be supported in the future once upgrading to clap-v3.
336            //
337            // NOTE: Seed bytes are hardcoded to be empty bytes for now. They
338            // will be updated once custom ElGamal keys are supported.
339            let elgamal_keypair =
340                ElGamalKeypair::new_from_signer(config.default_signer()?.as_ref(), b"").unwrap();
341            extensions.push(
342                ExtensionInitializationParams::ConfidentialTransferFeeConfig {
343                    authority: Some(authority),
344                    withdraw_withheld_authority_elgamal_pubkey: (*elgamal_keypair.pubkey()).into(),
345                },
346            );
347        }
348    }
349
350    if transfer_hook_program_id.is_some() || enable_transfer_hook {
351        let program_id = transfer_hook_program_id
352            .map(OptionalNonZeroPubkey)
353            .unwrap_or_default();
354        extensions.push(ExtensionInitializationParams::TransferHook {
355            authority: Some(authority),
356            program_id: program_id.into(),
357        });
358    }
359
360    if let Some(ui_multiplier) = ui_multiplier {
361        extensions.push(ExtensionInitializationParams::ScaledUiAmountConfig {
362            authority: Some(authority),
363            multiplier: ui_multiplier,
364        });
365    }
366
367    if let Some(text) = memo {
368        token.with_memo(text, vec![config.default_signer()?.pubkey()]);
369    }
370
371    // CLI checks that only one is set
372    if metadata_address.is_some() || enable_metadata {
373        let metadata_address = if enable_metadata {
374            Some(token_pubkey)
375        } else {
376            metadata_address
377        };
378        extensions.push(ExtensionInitializationParams::MetadataPointer {
379            authority: Some(authority),
380            metadata_address,
381        });
382    }
383
384    if group_address.is_some() || enable_group {
385        let group_address = if enable_group {
386            Some(token_pubkey)
387        } else {
388            group_address
389        };
390        extensions.push(ExtensionInitializationParams::GroupPointer {
391            authority: Some(authority),
392            group_address,
393        });
394    }
395
396    if member_address.is_some() || enable_member {
397        let member_address = if enable_member {
398            Some(token_pubkey)
399        } else {
400            member_address
401        };
402        extensions.push(ExtensionInitializationParams::GroupMemberPointer {
403            authority: Some(authority),
404            member_address,
405        });
406    }
407
408    if pausable {
409        extensions.push(ExtensionInitializationParams::PausableConfig { authority });
410    }
411
412    let res = token
413        .create_mint(
414            &authority,
415            freeze_authority.as_ref(),
416            extensions,
417            &bulk_signers,
418        )
419        .await?;
420
421    let tx_return = finish_tx(config, &res, false).await?;
422
423    if enable_metadata {
424        println_display(
425            config,
426            format!(
427                "To initialize metadata inside the mint, please run \
428                `spl-token initialize-metadata {token_pubkey} <YOUR_TOKEN_NAME> <YOUR_TOKEN_SYMBOL> <YOUR_TOKEN_URI>`, \
429                and sign with the mint authority.",
430            ),
431        );
432    }
433
434    if enable_group {
435        println_display(
436            config,
437            format!(
438                "To initialize group configurations inside the mint, please run `spl-token initialize-group {token_pubkey} <MAX_SIZE>`, and sign with the mint authority.",
439            ),
440        );
441    }
442
443    if enable_member {
444        println_display(
445            config,
446            format!(
447                "To initialize group member configurations inside the mint, please run `spl-token initialize-member {token_pubkey}`, and sign with the mint authority and the group's update authority.",
448            ),
449        );
450    }
451
452    Ok(match tx_return {
453        TransactionReturnData::CliSignature(cli_signature) => format_output(
454            CliCreateToken {
455                address: token_pubkey.to_string(),
456                decimals,
457                transaction_data: cli_signature,
458            },
459            &CommandName::CreateToken,
460            config,
461        ),
462        TransactionReturnData::CliSignOnlyData(cli_sign_only_data) => {
463            format_output(cli_sign_only_data, &CommandName::CreateToken, config)
464        }
465    })
466}
467
468async fn command_set_interest_rate(
469    config: &Config<'_>,
470    token_pubkey: Pubkey,
471    rate_authority: Pubkey,
472    rate_bps: i16,
473    bulk_signers: Vec<Arc<dyn Signer>>,
474) -> CommandResult {
475    let mut token = token_client_from_config(config, &token_pubkey, None)?;
476    // Because set_interest_rate depends on the time, it can cost more between
477    // simulation and execution. To help that, just set a static compute limit
478    // if none has been set
479    if !matches!(config.compute_unit_limit, ComputeUnitLimit::Static(_)) {
480        token = token.with_compute_unit_limit(ComputeUnitLimit::Static(2_500));
481    }
482
483    if !config.sign_only {
484        let mint_account = config.get_account_checked(&token_pubkey).await?;
485
486        let mint_state = StateWithExtensionsOwned::<Mint>::unpack(mint_account.data)
487            .map_err(|_| format!("Could not deserialize token mint {}", token_pubkey))?;
488
489        if let Ok(interest_rate_config) = mint_state.get_extension::<InterestBearingConfig>() {
490            let mint_rate_authority_pubkey =
491                Option::<Pubkey>::from(interest_rate_config.rate_authority);
492
493            if mint_rate_authority_pubkey != Some(rate_authority) {
494                return Err(format!(
495                    "Mint {} has interest rate authority {}, but {} was provided",
496                    token_pubkey,
497                    mint_rate_authority_pubkey
498                        .map(|pubkey| pubkey.to_string())
499                        .unwrap_or_else(|| "disabled".to_string()),
500                    rate_authority
501                )
502                .into());
503            }
504        } else {
505            return Err(format!("Mint {} is not interest-bearing", token_pubkey).into());
506        }
507    }
508
509    println_display(
510        config,
511        format!(
512            "Setting Interest Rate for {} to {} bps",
513            token_pubkey, rate_bps
514        ),
515    );
516
517    let res = token
518        .update_interest_rate(&rate_authority, rate_bps, &bulk_signers)
519        .await?;
520
521    let tx_return = finish_tx(config, &res, false).await?;
522    Ok(match tx_return {
523        TransactionReturnData::CliSignature(signature) => {
524            config.output_format.formatted_string(&signature)
525        }
526        TransactionReturnData::CliSignOnlyData(sign_only_data) => {
527            config.output_format.formatted_string(&sign_only_data)
528        }
529    })
530}
531
532async fn command_set_transfer_hook_program(
533    config: &Config<'_>,
534    token_pubkey: Pubkey,
535    authority: Pubkey,
536    new_program_id: Option<Pubkey>,
537    bulk_signers: Vec<Arc<dyn Signer>>,
538) -> CommandResult {
539    let token = token_client_from_config(config, &token_pubkey, None)?;
540
541    if !config.sign_only {
542        let mint_account = config.get_account_checked(&token_pubkey).await?;
543
544        let mint_state = StateWithExtensionsOwned::<Mint>::unpack(mint_account.data)
545            .map_err(|_| format!("Could not deserialize token mint {}", token_pubkey))?;
546
547        if let Ok(extension) = mint_state.get_extension::<TransferHook>() {
548            let authority_pubkey = Option::<Pubkey>::from(extension.authority);
549
550            if authority_pubkey != Some(authority) {
551                return Err(format!(
552                    "Mint {} has transfer hook authority {}, but {} was provided",
553                    token_pubkey,
554                    authority_pubkey
555                        .map(|pubkey| pubkey.to_string())
556                        .unwrap_or_else(|| "disabled".to_string()),
557                    authority
558                )
559                .into());
560            }
561        } else {
562            return Err(
563                format!("Mint {} does not have permissioned-transfers", token_pubkey).into(),
564            );
565        }
566    }
567
568    println_display(
569        config,
570        format!(
571            "Setting Transfer Hook Program id for {} to {}",
572            token_pubkey,
573            new_program_id
574                .map(|pubkey| pubkey.to_string())
575                .unwrap_or_else(|| "disabled".to_string())
576        ),
577    );
578
579    let res = token
580        .update_transfer_hook_program_id(&authority, new_program_id, &bulk_signers)
581        .await?;
582
583    let tx_return = finish_tx(config, &res, false).await?;
584    Ok(match tx_return {
585        TransactionReturnData::CliSignature(signature) => {
586            config.output_format.formatted_string(&signature)
587        }
588        TransactionReturnData::CliSignOnlyData(sign_only_data) => {
589            config.output_format.formatted_string(&sign_only_data)
590        }
591    })
592}
593
594#[allow(clippy::too_many_arguments)]
595async fn command_initialize_metadata(
596    config: &Config<'_>,
597    token_pubkey: Pubkey,
598    update_authority: Pubkey,
599    mint_authority: Pubkey,
600    name: String,
601    symbol: String,
602    uri: String,
603    bulk_signers: Vec<Arc<dyn Signer>>,
604) -> CommandResult {
605    let token = token_client_from_config(config, &token_pubkey, None)?;
606
607    let res = token
608        .token_metadata_initialize_with_rent_transfer(
609            &config.fee_payer()?.pubkey(),
610            &update_authority,
611            &mint_authority,
612            name,
613            symbol,
614            uri,
615            &bulk_signers,
616        )
617        .await?;
618
619    let tx_return = finish_tx(config, &res, false).await?;
620    Ok(match tx_return {
621        TransactionReturnData::CliSignature(signature) => {
622            config.output_format.formatted_string(&signature)
623        }
624        TransactionReturnData::CliSignOnlyData(sign_only_data) => {
625            config.output_format.formatted_string(&sign_only_data)
626        }
627    })
628}
629
630async fn command_update_metadata(
631    config: &Config<'_>,
632    token_pubkey: Pubkey,
633    authority: Pubkey,
634    field: Field,
635    value: Option<String>,
636    transfer_lamports: Option<u64>,
637    bulk_signers: Vec<Arc<dyn Signer>>,
638) -> CommandResult {
639    let token = token_client_from_config(config, &token_pubkey, None)?;
640
641    let res = if let Some(value) = value {
642        token
643            .token_metadata_update_field_with_rent_transfer(
644                &config.fee_payer()?.pubkey(),
645                &authority,
646                field,
647                value,
648                transfer_lamports,
649                &bulk_signers,
650            )
651            .await?
652    } else if let Field::Key(key) = field {
653        token
654            .token_metadata_remove_key(
655                &authority,
656                key,
657                true, // idempotent
658                &bulk_signers,
659            )
660            .await?
661    } else {
662        return Err(format!(
663            "Attempting to remove field {field:?}, which cannot be removed. \
664            Please re-run the command with a value of \"\" rather than the `--remove` flag."
665        )
666        .into());
667    };
668
669    let tx_return = finish_tx(config, &res, false).await?;
670    Ok(match tx_return {
671        TransactionReturnData::CliSignature(signature) => {
672            config.output_format.formatted_string(&signature)
673        }
674        TransactionReturnData::CliSignOnlyData(sign_only_data) => {
675            config.output_format.formatted_string(&sign_only_data)
676        }
677    })
678}
679
680#[allow(clippy::too_many_arguments)]
681async fn command_initialize_group(
682    config: &Config<'_>,
683    token_pubkey: Pubkey,
684    mint_authority: Pubkey,
685    update_authority: Pubkey,
686    max_size: u64,
687    bulk_signers: Vec<Arc<dyn Signer>>,
688) -> CommandResult {
689    let token = token_client_from_config(config, &token_pubkey, None)?;
690
691    let res = token
692        .token_group_initialize_with_rent_transfer(
693            &config.fee_payer()?.pubkey(),
694            &mint_authority,
695            &update_authority,
696            max_size,
697            &bulk_signers,
698        )
699        .await?;
700
701    let tx_return = finish_tx(config, &res, false).await?;
702    Ok(match tx_return {
703        TransactionReturnData::CliSignature(signature) => {
704            config.output_format.formatted_string(&signature)
705        }
706        TransactionReturnData::CliSignOnlyData(sign_only_data) => {
707            config.output_format.formatted_string(&sign_only_data)
708        }
709    })
710}
711
712#[allow(clippy::too_many_arguments)]
713async fn command_update_group_max_size(
714    config: &Config<'_>,
715    token_pubkey: Pubkey,
716    update_authority: Pubkey,
717    new_max_size: u64,
718    bulk_signers: Vec<Arc<dyn Signer>>,
719) -> CommandResult {
720    let token = token_client_from_config(config, &token_pubkey, None)?;
721
722    let res = token
723        .token_group_update_max_size(&update_authority, new_max_size, &bulk_signers)
724        .await?;
725
726    let tx_return = finish_tx(config, &res, false).await?;
727    Ok(match tx_return {
728        TransactionReturnData::CliSignature(signature) => {
729            config.output_format.formatted_string(&signature)
730        }
731        TransactionReturnData::CliSignOnlyData(sign_only_data) => {
732            config.output_format.formatted_string(&sign_only_data)
733        }
734    })
735}
736
737async fn command_initialize_member(
738    config: &Config<'_>,
739    member_token_pubkey: Pubkey,
740    mint_authority: Pubkey,
741    group_token_pubkey: Pubkey,
742    group_update_authority: Pubkey,
743    bulk_signers: Vec<Arc<dyn Signer>>,
744) -> CommandResult {
745    let token = token_client_from_config(config, &member_token_pubkey, None)?;
746
747    let res = token
748        .token_group_initialize_member_with_rent_transfer(
749            &config.fee_payer()?.pubkey(),
750            &mint_authority,
751            &group_token_pubkey,
752            &group_update_authority,
753            &bulk_signers,
754        )
755        .await?;
756
757    let tx_return = finish_tx(config, &res, false).await?;
758    Ok(match tx_return {
759        TransactionReturnData::CliSignature(signature) => {
760            config.output_format.formatted_string(&signature)
761        }
762        TransactionReturnData::CliSignOnlyData(sign_only_data) => {
763            config.output_format.formatted_string(&sign_only_data)
764        }
765    })
766}
767
768async fn command_set_transfer_fee(
769    config: &Config<'_>,
770    token_pubkey: Pubkey,
771    transfer_fee_authority: Pubkey,
772    transfer_fee_basis_points: u16,
773    maximum_fee: Amount,
774    mint_decimals: Option<u8>,
775    bulk_signers: Vec<Arc<dyn Signer>>,
776) -> CommandResult {
777    let decimals = if !config.sign_only {
778        let mint_account = config.get_account_checked(&token_pubkey).await?;
779
780        let mint_state = StateWithExtensionsOwned::<Mint>::unpack(mint_account.data)
781            .map_err(|_| format!("Could not deserialize token mint {}", token_pubkey))?;
782
783        if mint_decimals.is_some() && mint_decimals != Some(mint_state.base.decimals) {
784            return Err(format!(
785                "Decimals {} was provided, but actual value is {}",
786                mint_decimals.unwrap(),
787                mint_state.base.decimals
788            )
789            .into());
790        }
791
792        if let Ok(transfer_fee_config) = mint_state.get_extension::<TransferFeeConfig>() {
793            let mint_fee_authority_pubkey =
794                Option::<Pubkey>::from(transfer_fee_config.transfer_fee_config_authority);
795
796            if mint_fee_authority_pubkey != Some(transfer_fee_authority) {
797                return Err(format!(
798                    "Mint {} has transfer fee authority {}, but {} was provided",
799                    token_pubkey,
800                    mint_fee_authority_pubkey
801                        .map(|pubkey| pubkey.to_string())
802                        .unwrap_or_else(|| "disabled".to_string()),
803                    transfer_fee_authority
804                )
805                .into());
806            }
807        } else {
808            return Err(format!("Mint {} does not have a transfer fee", token_pubkey).into());
809        }
810        mint_state.base.decimals
811    } else {
812        mint_decimals.unwrap()
813    };
814
815    let token = token_client_from_config(config, &token_pubkey, Some(decimals))?;
816    let maximum_fee = amount_to_raw_amount(maximum_fee, decimals, None, "MAXIMUM_FEE");
817
818    println_display(
819        config,
820        format!(
821            "Setting transfer fee for {} to {} bps, {} maximum",
822            token_pubkey,
823            transfer_fee_basis_points,
824            spl_token_2022::amount_to_ui_amount(maximum_fee, decimals)
825        ),
826    );
827
828    let res = token
829        .set_transfer_fee(
830            &transfer_fee_authority,
831            transfer_fee_basis_points,
832            maximum_fee,
833            &bulk_signers,
834        )
835        .await?;
836
837    let tx_return = finish_tx(config, &res, false).await?;
838    Ok(match tx_return {
839        TransactionReturnData::CliSignature(signature) => {
840            config.output_format.formatted_string(&signature)
841        }
842        TransactionReturnData::CliSignOnlyData(sign_only_data) => {
843            config.output_format.formatted_string(&sign_only_data)
844        }
845    })
846}
847
848async fn command_create_account(
849    config: &Config<'_>,
850    token_pubkey: Pubkey,
851    owner: Pubkey,
852    maybe_account: Option<Pubkey>,
853    immutable_owner: bool,
854    bulk_signers: Vec<Arc<dyn Signer>>,
855) -> CommandResult {
856    let token = token_client_from_config(config, &token_pubkey, None)?;
857    let mut extensions = vec![];
858
859    let (account, is_associated) = if let Some(account) = maybe_account {
860        (
861            account,
862            token.get_associated_token_address(&owner) == account,
863        )
864    } else {
865        (token.get_associated_token_address(&owner), true)
866    };
867
868    println_display(config, format!("Creating account {}", account));
869
870    if !config.sign_only {
871        if let Some(account_data) = config.program_client.get_account(account).await? {
872            if account_data.owner != system_program::id() || !is_associated {
873                return Err(format!("Error: Account already exists: {}", account).into());
874            }
875        }
876    }
877
878    if immutable_owner {
879        if config.program_id == spl_token_interface::id() {
880            return Err(format!(
881                "Specified --immutable, but token program {} does not support the extension",
882                config.program_id
883            )
884            .into());
885        } else if is_associated {
886            println_display(
887                config,
888                "Note: --immutable specified, but Token-2022 ATAs are always immutable, ignoring"
889                    .to_string(),
890            );
891        } else {
892            extensions.push(ExtensionType::ImmutableOwner);
893        }
894    }
895
896    let res = if is_associated {
897        token.create_associated_token_account(&owner).await
898    } else {
899        let signer = bulk_signers
900            .iter()
901            .find(|signer| signer.pubkey() == account)
902            .unwrap_or_else(|| panic!("No signer provided for account {}", account));
903
904        token
905            .create_auxiliary_token_account_with_extension_space(&**signer, &owner, extensions)
906            .await
907    }?;
908
909    let tx_return = finish_tx(config, &res, false).await?;
910    Ok(match tx_return {
911        TransactionReturnData::CliSignature(signature) => {
912            config.output_format.formatted_string(&signature)
913        }
914        TransactionReturnData::CliSignOnlyData(sign_only_data) => {
915            config.output_format.formatted_string(&sign_only_data)
916        }
917    })
918}
919
920async fn command_create_multisig(
921    config: &Config<'_>,
922    multisig: Arc<dyn Signer>,
923    minimum_signers: u8,
924    multisig_members: Vec<Pubkey>,
925) -> CommandResult {
926    println_display(
927        config,
928        format!(
929            "Creating {}/{} multisig {} under program {}",
930            minimum_signers,
931            multisig_members.len(),
932            multisig.pubkey(),
933            config.program_id,
934        ),
935    );
936
937    // default is safe here because create_multisig doesn't use it
938    let token = token_client_from_config(config, &Pubkey::default(), None)?;
939
940    let res = token
941        .create_multisig(
942            &*multisig,
943            &multisig_members.iter().collect::<Vec<_>>(),
944            minimum_signers,
945        )
946        .await?;
947
948    let tx_return = finish_tx(config, &res, false).await?;
949    Ok(match tx_return {
950        TransactionReturnData::CliSignature(signature) => {
951            config.output_format.formatted_string(&signature)
952        }
953        TransactionReturnData::CliSignOnlyData(sign_only_data) => {
954            config.output_format.formatted_string(&sign_only_data)
955        }
956    })
957}
958
959#[allow(clippy::too_many_arguments)]
960async fn command_authorize(
961    config: &Config<'_>,
962    account: Pubkey,
963    authority_type: CliAuthorityType,
964    authority: Pubkey,
965    new_authority: Option<Pubkey>,
966    force_authorize: bool,
967    bulk_signers: BulkSigners,
968) -> CommandResult {
969    let auth_str: &'static str = (&authority_type).into();
970
971    let (mint_pubkey, previous_authority) = if !config.sign_only {
972        let target_account = config.get_account_checked(&account).await?;
973
974        let (mint_pubkey, previous_authority) = if let Ok(mint) =
975            StateWithExtensionsOwned::<Mint>::unpack(target_account.data.clone())
976        {
977            let previous_authority = match authority_type {
978                CliAuthorityType::Owner | CliAuthorityType::Close => Err(format!(
979                    "Authority type `{}` not supported for SPL Token mints",
980                    auth_str
981                )),
982                CliAuthorityType::Mint => Ok(Option::<Pubkey>::from(mint.base.mint_authority)),
983                CliAuthorityType::Freeze => Ok(Option::<Pubkey>::from(mint.base.freeze_authority)),
984                CliAuthorityType::CloseMint => {
985                    if let Ok(mint_close_authority) = mint.get_extension::<MintCloseAuthority>() {
986                        Ok(Option::<Pubkey>::from(mint_close_authority.close_authority))
987                    } else {
988                        Err(format!(
989                            "Mint `{}` does not support close authority",
990                            account
991                        ))
992                    }
993                }
994                CliAuthorityType::TransferFeeConfig => {
995                    if let Ok(transfer_fee_config) = mint.get_extension::<TransferFeeConfig>() {
996                        Ok(Option::<Pubkey>::from(
997                            transfer_fee_config.transfer_fee_config_authority,
998                        ))
999                    } else {
1000                        Err(format!("Mint `{}` does not support transfer fees", account))
1001                    }
1002                }
1003                CliAuthorityType::WithheldWithdraw => {
1004                    if let Ok(transfer_fee_config) = mint.get_extension::<TransferFeeConfig>() {
1005                        Ok(Option::<Pubkey>::from(
1006                            transfer_fee_config.withdraw_withheld_authority,
1007                        ))
1008                    } else {
1009                        Err(format!("Mint `{}` does not support transfer fees", account))
1010                    }
1011                }
1012                CliAuthorityType::InterestRate => {
1013                    if let Ok(interest_rate_config) = mint.get_extension::<InterestBearingConfig>()
1014                    {
1015                        Ok(Option::<Pubkey>::from(interest_rate_config.rate_authority))
1016                    } else {
1017                        Err(format!("Mint `{}` is not interest-bearing", account))
1018                    }
1019                }
1020                CliAuthorityType::PermanentDelegate => {
1021                    if let Ok(permanent_delegate) = mint.get_extension::<PermanentDelegate>() {
1022                        Ok(Option::<Pubkey>::from(permanent_delegate.delegate))
1023                    } else {
1024                        Err(format!(
1025                            "Mint `{}` does not support permanent delegate",
1026                            account
1027                        ))
1028                    }
1029                }
1030                CliAuthorityType::ConfidentialTransferMint => {
1031                    if let Ok(confidential_transfer_mint) =
1032                        mint.get_extension::<ConfidentialTransferMint>()
1033                    {
1034                        Ok(Option::<Pubkey>::from(confidential_transfer_mint.authority))
1035                    } else {
1036                        Err(format!(
1037                            "Mint `{}` does not support confidential transfers",
1038                            account
1039                        ))
1040                    }
1041                }
1042                CliAuthorityType::TransferHookProgramId => {
1043                    if let Ok(extension) = mint.get_extension::<TransferHook>() {
1044                        Ok(Option::<Pubkey>::from(extension.authority))
1045                    } else {
1046                        Err(format!(
1047                            "Mint `{}` does not support a transfer hook program",
1048                            account
1049                        ))
1050                    }
1051                }
1052                CliAuthorityType::ConfidentialTransferFee => {
1053                    if let Ok(confidential_transfer_fee_config) =
1054                        mint.get_extension::<ConfidentialTransferFeeConfig>()
1055                    {
1056                        Ok(Option::<Pubkey>::from(
1057                            confidential_transfer_fee_config.authority,
1058                        ))
1059                    } else {
1060                        Err(format!(
1061                            "Mint `{}` does not support confidential transfer fees",
1062                            account
1063                        ))
1064                    }
1065                }
1066                CliAuthorityType::MetadataPointer => {
1067                    if let Ok(extension) = mint.get_extension::<MetadataPointer>() {
1068                        Ok(Option::<Pubkey>::from(extension.authority))
1069                    } else {
1070                        Err(format!(
1071                            "Mint `{}` does not support a metadata pointer",
1072                            account
1073                        ))
1074                    }
1075                }
1076                CliAuthorityType::Metadata => {
1077                    if let Ok(extension) = mint.get_variable_len_extension::<TokenMetadata>() {
1078                        Ok(Option::<Pubkey>::from(extension.update_authority))
1079                    } else {
1080                        Err(format!("Mint `{account}` does not support metadata"))
1081                    }
1082                }
1083                CliAuthorityType::GroupPointer => {
1084                    if let Ok(extension) = mint.get_extension::<GroupPointer>() {
1085                        Ok(Option::<Pubkey>::from(extension.authority))
1086                    } else {
1087                        Err(format!(
1088                            "Mint `{}` does not support a group pointer",
1089                            account
1090                        ))
1091                    }
1092                }
1093                CliAuthorityType::GroupMemberPointer => {
1094                    if let Ok(extension) = mint.get_extension::<GroupMemberPointer>() {
1095                        Ok(Option::<Pubkey>::from(extension.authority))
1096                    } else {
1097                        Err(format!(
1098                            "Mint `{}` does not support a group member pointer",
1099                            account
1100                        ))
1101                    }
1102                }
1103                CliAuthorityType::Group => {
1104                    if let Ok(extension) = mint.get_extension::<TokenGroup>() {
1105                        Ok(Option::<Pubkey>::from(extension.update_authority))
1106                    } else {
1107                        Err(format!("Mint `{}` does not support token groups", account))
1108                    }
1109                }
1110                CliAuthorityType::ScaledUiAmount => {
1111                    if let Ok(extension) = mint.get_extension::<ScaledUiAmountConfig>() {
1112                        Ok(Option::<Pubkey>::from(extension.authority))
1113                    } else {
1114                        Err(format!("Mint `{}` does not support UI multiplier", account))
1115                    }
1116                }
1117                CliAuthorityType::Pause => {
1118                    if let Ok(extension) = mint.get_extension::<PausableConfig>() {
1119                        Ok(Option::<Pubkey>::from(extension.authority))
1120                    } else {
1121                        Err(format!(
1122                            "Mint `{}` does not support pause or resume",
1123                            account
1124                        ))
1125                    }
1126                }
1127            }?;
1128
1129            Ok((account, previous_authority))
1130        } else if let Ok(token_account) =
1131            StateWithExtensionsOwned::<Account>::unpack(target_account.data)
1132        {
1133            let check_associated_token_account = || -> Result<(), Error> {
1134                let maybe_associated_token_account = get_associated_token_address_with_program_id(
1135                    &token_account.base.owner,
1136                    &token_account.base.mint,
1137                    &config.program_id,
1138                );
1139                if account == maybe_associated_token_account
1140                    && !force_authorize
1141                    && Some(authority) != new_authority
1142                {
1143                    Err(format!(
1144                        "Error: attempting to change the `{}` of an associated token account",
1145                        auth_str
1146                    )
1147                    .into())
1148                } else {
1149                    Ok(())
1150                }
1151            };
1152
1153            let previous_authority = match authority_type {
1154                CliAuthorityType::Mint
1155                | CliAuthorityType::Freeze
1156                | CliAuthorityType::CloseMint
1157                | CliAuthorityType::TransferFeeConfig
1158                | CliAuthorityType::WithheldWithdraw
1159                | CliAuthorityType::InterestRate
1160                | CliAuthorityType::PermanentDelegate
1161                | CliAuthorityType::ConfidentialTransferMint
1162                | CliAuthorityType::TransferHookProgramId
1163                | CliAuthorityType::ConfidentialTransferFee
1164                | CliAuthorityType::MetadataPointer
1165                | CliAuthorityType::Metadata
1166                | CliAuthorityType::GroupPointer
1167                | CliAuthorityType::Group
1168                | CliAuthorityType::GroupMemberPointer
1169                | CliAuthorityType::ScaledUiAmount
1170                | CliAuthorityType::Pause => Err(format!(
1171                    "Authority type `{auth_str}` not supported for SPL Token accounts",
1172                )),
1173                CliAuthorityType::Owner => {
1174                    check_associated_token_account()?;
1175                    Ok(Some(token_account.base.owner))
1176                }
1177                CliAuthorityType::Close => {
1178                    check_associated_token_account()?;
1179                    Ok(Some(
1180                        token_account
1181                            .base
1182                            .close_authority
1183                            .unwrap_or(token_account.base.owner),
1184                    ))
1185                }
1186            }?;
1187
1188            Ok((token_account.base.mint, previous_authority))
1189        } else {
1190            Err("Unsupported account data format".to_string())
1191        }?;
1192
1193        (mint_pubkey, previous_authority)
1194    } else {
1195        // default is safe here because authorize doesn't use it
1196        (Pubkey::default(), None)
1197    };
1198
1199    let token = token_client_from_config(config, &mint_pubkey, None)?;
1200
1201    println_display(
1202        config,
1203        format!(
1204            "Updating {}\n  Current {}: {}\n  New {}: {}",
1205            account,
1206            auth_str,
1207            previous_authority
1208                .map(|pubkey| pubkey.to_string())
1209                .unwrap_or_else(|| if config.sign_only {
1210                    "unknown".to_string()
1211                } else {
1212                    "disabled".to_string()
1213                }),
1214            auth_str,
1215            new_authority
1216                .map(|pubkey| pubkey.to_string())
1217                .unwrap_or_else(|| "disabled".to_string())
1218        ),
1219    );
1220
1221    let res = match authority_type {
1222        CliAuthorityType::Metadata => {
1223            token
1224                .token_metadata_update_authority(&authority, new_authority, &bulk_signers)
1225                .await?
1226        }
1227        CliAuthorityType::Group => {
1228            token
1229                .token_group_update_authority(&authority, new_authority, &bulk_signers)
1230                .await?
1231        }
1232        _ => {
1233            token
1234                .set_authority(
1235                    &account,
1236                    &authority,
1237                    new_authority.as_ref(),
1238                    authority_type.try_into()?,
1239                    &bulk_signers,
1240                )
1241                .await?
1242        }
1243    };
1244
1245    let tx_return = finish_tx(config, &res, false).await?;
1246    Ok(match tx_return {
1247        TransactionReturnData::CliSignature(signature) => {
1248            config.output_format.formatted_string(&signature)
1249        }
1250        TransactionReturnData::CliSignOnlyData(sign_only_data) => {
1251            config.output_format.formatted_string(&sign_only_data)
1252        }
1253    })
1254}
1255
1256#[allow(clippy::too_many_arguments)]
1257async fn command_transfer(
1258    config: &Config<'_>,
1259    token_pubkey: Pubkey,
1260    ui_amount: Amount,
1261    recipient: Pubkey,
1262    sender: Option<Pubkey>,
1263    sender_owner: Pubkey,
1264    allow_unfunded_recipient: bool,
1265    fund_recipient: bool,
1266    mint_decimals: Option<u8>,
1267    no_recipient_is_ata_owner: bool,
1268    use_unchecked_instruction: bool,
1269    ui_fee: Option<Amount>,
1270    memo: Option<String>,
1271    bulk_signers: BulkSigners,
1272    no_wait: bool,
1273    allow_non_system_account_recipient: bool,
1274    transfer_hook_accounts: Option<Vec<AccountMeta>>,
1275    confidential_transfer_args: Option<&ConfidentialTransferArgs>,
1276) -> CommandResult {
1277    let mint_info = config.get_mint_info(&token_pubkey, mint_decimals).await?;
1278
1279    // if the user got the decimals wrong, they may well have calculated the
1280    // transfer amount wrong we only check in online mode, because in offline,
1281    // mint_info.decimals is always 9
1282    if !config.sign_only && mint_decimals.is_some() && mint_decimals != Some(mint_info.decimals) {
1283        return Err(format!(
1284            "Decimals {} was provided, but actual value is {}",
1285            mint_decimals.unwrap(),
1286            mint_info.decimals
1287        )
1288        .into());
1289    }
1290
1291    // decimals determines whether transfer_checked is used or not
1292    // in online mode, mint_decimals may be None but mint_info.decimals is always
1293    // correct in offline mode, mint_info.decimals may be wrong, but
1294    // mint_decimals is always provided and in online mode, when mint_decimals
1295    // is provided, it is verified correct hence the fallthrough logic here
1296    let decimals = if use_unchecked_instruction {
1297        None
1298    } else if mint_decimals.is_some() {
1299        mint_decimals
1300    } else {
1301        Some(mint_info.decimals)
1302    };
1303
1304    let token = if let Some(transfer_hook_accounts) = transfer_hook_accounts {
1305        token_client_from_config(config, &token_pubkey, decimals)?
1306            .with_transfer_hook_accounts(transfer_hook_accounts)
1307    } else if config.sign_only {
1308        // we need to pass in empty transfer hook accounts on sign-only,
1309        // otherwise the token client will try to fetch the mint account and fail
1310        token_client_from_config(config, &token_pubkey, decimals)?
1311            .with_transfer_hook_accounts(vec![])
1312    } else {
1313        token_client_from_config(config, &token_pubkey, decimals)?
1314    };
1315
1316    // pubkey of the actual account we are sending from
1317    let sender = if let Some(sender) = sender {
1318        sender
1319    } else {
1320        token.get_associated_token_address(&sender_owner)
1321    };
1322
1323    // the sender balance
1324    let sender_balance = if config.sign_only {
1325        None
1326    } else {
1327        Some(token.get_account_info(&sender).await?.base.amount)
1328    };
1329
1330    // the amount the user wants to transfer, as a u64
1331    let transfer_balance = match ui_amount {
1332        Amount::Raw(ui_amount) => ui_amount,
1333        Amount::Decimal(ui_amount) => {
1334            spl_token_2022::ui_amount_to_amount(ui_amount, mint_info.decimals)
1335        }
1336        Amount::All => {
1337            if config.sign_only {
1338                return Err("Use of ALL keyword to burn tokens requires online signing"
1339                    .to_string()
1340                    .into());
1341            }
1342            sender_balance.unwrap()
1343        }
1344    };
1345
1346    println_display(
1347        config,
1348        format!(
1349            "{}Transfer {} tokens\n  Sender: {}\n  Recipient: {}",
1350            if confidential_transfer_args.is_some() {
1351                "Confidential "
1352            } else {
1353                ""
1354            },
1355            spl_token_2022::amount_to_ui_amount(transfer_balance, mint_info.decimals),
1356            sender,
1357            recipient
1358        ),
1359    );
1360
1361    if let Some(sender_balance) = sender_balance {
1362        if transfer_balance > sender_balance && confidential_transfer_args.is_none() {
1363            return Err(format!(
1364                "Error: Sender has insufficient funds, current balance is {}",
1365                spl_token_2022::amount_to_ui_amount_string_trimmed(
1366                    sender_balance,
1367                    mint_info.decimals
1368                )
1369            )
1370            .into());
1371        }
1372    }
1373
1374    let maybe_fee =
1375        ui_fee.map(|v| amount_to_raw_amount(v, mint_info.decimals, None, "EXPECTED_FEE"));
1376
1377    // determine whether recipient is a token account or an expected owner of one
1378    let recipient_is_token_account = if !config.sign_only {
1379        // in online mode we can fetch it and see
1380        let maybe_recipient_account_data = config.program_client.get_account(recipient).await?;
1381
1382        // if the account exists, and:
1383        // * its a token for this program, we are happy
1384        // * its a system account, we are happy
1385        // * its a non-account for this program, we error helpfully
1386        // * its a token account for a different program, we error helpfully
1387        // * otherwise its probably a program account owner of an ata, in which case we
1388        //   gate transfer with a flag
1389        if let Some(recipient_account_data) = maybe_recipient_account_data {
1390            let recipient_account_owner = recipient_account_data.owner;
1391            let maybe_account_state =
1392                StateWithExtensionsOwned::<Account>::unpack(recipient_account_data.data);
1393
1394            if recipient_account_owner == config.program_id && maybe_account_state.is_ok() {
1395                if let Ok(memo_transfer) = maybe_account_state?.get_extension::<MemoTransfer>() {
1396                    if memo_transfer.require_incoming_transfer_memos.into() && memo.is_none() {
1397                        return Err(
1398                            "Error: Recipient expects a transfer memo, but none was provided. \
1399                                    Provide a memo using `--with-memo`."
1400                                .into(),
1401                        );
1402                    }
1403                }
1404
1405                true
1406            } else if recipient_account_owner == system_program::id() {
1407                false
1408            } else if recipient_account_owner == config.program_id {
1409                return Err(
1410                    "Error: Recipient is owned by this token program, but is not a token account."
1411                        .into(),
1412                );
1413            } else if VALID_TOKEN_PROGRAM_IDS.contains(&recipient_account_owner) {
1414                return Err(format!(
1415                    "Error: Recipient is owned by {}, but the token mint is owned by {}.",
1416                    recipient_account_owner, config.program_id
1417                )
1418                .into());
1419            } else if allow_non_system_account_recipient {
1420                false
1421            } else {
1422                return Err("Error: The recipient address is not owned by the System Program. \
1423                                     Add `--allow-non-system-account-recipient` to complete the transfer.".into());
1424            }
1425        }
1426        // if it doesn't exist, it definitely isn't a token account!
1427        // we gate transfer with a different flag
1428        else if maybe_recipient_account_data.is_none() && allow_unfunded_recipient {
1429            false
1430        } else {
1431            return Err("Error: The recipient address is not funded. \
1432                        Add `--allow-unfunded-recipient` to complete the transfer."
1433                .into());
1434        }
1435    } else {
1436        // in offline mode we gotta trust them
1437        no_recipient_is_ata_owner
1438    };
1439
1440    // now if its a token account, life is ez
1441    let (recipient_token_account, fundable_owner) = if recipient_is_token_account {
1442        (recipient, None)
1443    }
1444    // but if not, we need to determine if we can or should create an ata for recipient
1445    else {
1446        // first, get the ata address
1447        let recipient_token_account = token.get_associated_token_address(&recipient);
1448
1449        println_display(
1450            config,
1451            format!(
1452                "  Recipient associated token account: {}",
1453                recipient_token_account
1454            ),
1455        );
1456
1457        // if we can fetch it to determine if it exists, do so
1458        let needs_funding = if !config.sign_only {
1459            if let Some(recipient_token_account_data) = config
1460                .program_client
1461                .get_account(recipient_token_account)
1462                .await?
1463            {
1464                let recipient_token_account_owner = recipient_token_account_data.owner;
1465
1466                if let Ok(account_state) =
1467                    StateWithExtensionsOwned::<Account>::unpack(recipient_token_account_data.data)
1468                {
1469                    if let Ok(memo_transfer) = account_state.get_extension::<MemoTransfer>() {
1470                        if memo_transfer.require_incoming_transfer_memos.into() && memo.is_none() {
1471                            return Err(
1472                                "Error: Recipient expects a transfer memo, but none was provided. \
1473                                        Provide a memo using `--with-memo`."
1474                                    .into(),
1475                            );
1476                        }
1477                    }
1478                }
1479
1480                if recipient_token_account_owner == system_program::id() {
1481                    true
1482                } else if recipient_token_account_owner == config.program_id {
1483                    false
1484                } else {
1485                    return Err(
1486                        format!("Error: Unsupported recipient address: {}", recipient).into(),
1487                    );
1488                }
1489            } else {
1490                true
1491            }
1492        }
1493        // otherwise trust the cli flag
1494        else {
1495            fund_recipient
1496        };
1497
1498        // and now we determine if we will actually fund it, based on its need and our
1499        // willingness
1500        let fundable_owner = if needs_funding {
1501            if confidential_transfer_args.is_some() {
1502                return Err(
1503                    "Error: Recipient's associated token account does not exist. \
1504                        Accounts cannot be funded for confidential transfers."
1505                        .into(),
1506                );
1507            } else if fund_recipient {
1508                println_display(
1509                    config,
1510                    format!("  Funding recipient: {}", recipient_token_account,),
1511                );
1512
1513                Some(recipient)
1514            } else {
1515                return Err(
1516                    "Error: Recipient's associated token account does not exist. \
1517                                    Add `--fund-recipient` to fund their account"
1518                        .into(),
1519                );
1520            }
1521        } else {
1522            None
1523        };
1524
1525        (recipient_token_account, fundable_owner)
1526    };
1527
1528    // set up memo if provided...
1529    if let Some(text) = memo {
1530        token.with_memo(text, vec![config.default_signer()?.pubkey()]);
1531    }
1532
1533    // fetch confidential transfer info for recipient and auditor
1534    let (recipient_elgamal_pubkey, auditor_elgamal_pubkey) = if let Some(args) =
1535        confidential_transfer_args
1536    {
1537        if !config.sign_only {
1538            // we can use the mint data from the start of the function, but will require
1539            // non-trivial amount of refactoring the code due to ownership; for now, we
1540            // fetch the mint a second time. This can potentially be optimized
1541            // in the future.
1542            let confidential_transfer_mint = config.get_account_checked(&token_pubkey).await?;
1543            let mint_state =
1544                StateWithExtensionsOwned::<Mint>::unpack(confidential_transfer_mint.data)
1545                    .map_err(|_| format!("Could not deserialize token mint {}", token_pubkey))?;
1546
1547            let auditor_elgamal_pubkey = if let Ok(confidential_transfer_mint) =
1548                mint_state.get_extension::<ConfidentialTransferMint>()
1549            {
1550                let expected_auditor_elgamal_pubkey = Option::<PodElGamalPubkey>::from(
1551                    confidential_transfer_mint.auditor_elgamal_pubkey,
1552                );
1553
1554                // if auditor ElGamal pubkey is provided, check consistency with the one in the
1555                // mint if auditor ElGamal pubkey is not provided, then use the
1556                // expected one from the   mint, which could also be `None` if
1557                // auditing is disabled
1558                if args.auditor_elgamal_pubkey.is_some()
1559                    && expected_auditor_elgamal_pubkey != args.auditor_elgamal_pubkey
1560                {
1561                    return Err(format!(
1562                        "Mint {} has confidential transfer auditor {}, but {} was provided",
1563                        token_pubkey,
1564                        expected_auditor_elgamal_pubkey
1565                            .map(|pubkey| pubkey.to_string())
1566                            .unwrap_or_else(|| "disabled".to_string()),
1567                        args.auditor_elgamal_pubkey.unwrap(),
1568                    )
1569                    .into());
1570                }
1571
1572                expected_auditor_elgamal_pubkey
1573            } else {
1574                return Err(format!(
1575                    "Mint {} does not support confidential transfers",
1576                    token_pubkey
1577                )
1578                .into());
1579            };
1580
1581            let recipient_account = config.get_account_checked(&recipient_token_account).await?;
1582            let recipient_elgamal_pubkey =
1583                StateWithExtensionsOwned::<Account>::unpack(recipient_account.data)?
1584                    .get_extension::<ConfidentialTransferAccount>()?
1585                    .elgamal_pubkey;
1586
1587            (Some(recipient_elgamal_pubkey), auditor_elgamal_pubkey)
1588        } else {
1589            let recipient_elgamal_pubkey = args
1590                .recipient_elgamal_pubkey
1591                .expect("Recipient ElGamal pubkey must be provided");
1592            let auditor_elgamal_pubkey = args
1593                .auditor_elgamal_pubkey
1594                .expect("Auditor ElGamal pubkey must be provided");
1595
1596            (Some(recipient_elgamal_pubkey), Some(auditor_elgamal_pubkey))
1597        }
1598    } else {
1599        (None, None)
1600    };
1601
1602    // ...and, finally, the transfer
1603    let res = match (fundable_owner, maybe_fee, confidential_transfer_args) {
1604        (Some(recipient_owner), None, None) => {
1605            token
1606                .create_recipient_associated_account_and_transfer(
1607                    &sender,
1608                    &recipient_token_account,
1609                    &recipient_owner,
1610                    &sender_owner,
1611                    transfer_balance,
1612                    maybe_fee,
1613                    &bulk_signers,
1614                )
1615                .await?
1616        }
1617        (Some(_), _, _) => {
1618            panic!("Recipient account cannot be created for transfer with fees or confidential transfers");
1619        }
1620        (None, Some(fee), None) => {
1621            token
1622                .transfer_with_fee(
1623                    &sender,
1624                    &recipient_token_account,
1625                    &sender_owner,
1626                    transfer_balance,
1627                    fee,
1628                    &bulk_signers,
1629                )
1630                .await?
1631        }
1632        (None, None, Some(args)) => {
1633            // deserialize `pod` ElGamal pubkeys
1634            let recipient_elgamal_pubkey: elgamal::ElGamalPubkey = recipient_elgamal_pubkey
1635                .unwrap()
1636                .try_into()
1637                .expect("Invalid recipient ElGamal pubkey");
1638            let auditor_elgamal_pubkey = auditor_elgamal_pubkey.map(|pubkey| {
1639                let auditor_elgamal_pubkey: elgamal::ElGamalPubkey =
1640                    pubkey.try_into().expect("Invalid auditor ElGamal pubkey");
1641                auditor_elgamal_pubkey
1642            });
1643
1644            let context_state_authority = config.fee_payer()?;
1645            let context_state_authority_pubkey = context_state_authority.pubkey();
1646            let equality_proof_context_state_account = Keypair::new();
1647            let equality_proof_pubkey = equality_proof_context_state_account.pubkey();
1648            let ciphertext_validity_proof_context_state_account = Keypair::new();
1649            let ciphertext_validity_proof_pubkey =
1650                ciphertext_validity_proof_context_state_account.pubkey();
1651            let range_proof_context_state_account = Keypair::new();
1652            let range_proof_pubkey = range_proof_context_state_account.pubkey();
1653
1654            let state = token.get_account_info(&sender).await.unwrap();
1655            let extension = state
1656                .get_extension::<ConfidentialTransferAccount>()
1657                .unwrap();
1658            let transfer_account_info = TransferAccountInfo::new(extension);
1659
1660            let TransferProofData {
1661                equality_proof_data,
1662                ciphertext_validity_proof_data_with_ciphertext,
1663                range_proof_data,
1664            } = transfer_account_info
1665                .generate_split_transfer_proof_data(
1666                    transfer_balance,
1667                    &args.sender_elgamal_keypair,
1668                    &args.sender_aes_key,
1669                    &recipient_elgamal_pubkey,
1670                    auditor_elgamal_pubkey.as_ref(),
1671                )
1672                .unwrap();
1673
1674            let transfer_amount_auditor_ciphertext_lo =
1675                ciphertext_validity_proof_data_with_ciphertext.ciphertext_lo;
1676            let transfer_amount_auditor_ciphertext_hi =
1677                ciphertext_validity_proof_data_with_ciphertext.ciphertext_hi;
1678
1679            // setup proofs
1680            let create_range_proof_context_signer = &[&range_proof_context_state_account];
1681            let create_equality_proof_context_signer = &[&equality_proof_context_state_account];
1682            let create_ciphertext_validity_proof_context_signer =
1683                &[&ciphertext_validity_proof_context_state_account];
1684
1685            let _ = try_join!(
1686                token.confidential_transfer_create_context_state_account(
1687                    &range_proof_pubkey,
1688                    &context_state_authority_pubkey,
1689                    &range_proof_data,
1690                    true,
1691                    create_range_proof_context_signer
1692                ),
1693                token.confidential_transfer_create_context_state_account(
1694                    &equality_proof_pubkey,
1695                    &context_state_authority_pubkey,
1696                    &equality_proof_data,
1697                    false,
1698                    create_equality_proof_context_signer
1699                ),
1700                token.confidential_transfer_create_context_state_account(
1701                    &ciphertext_validity_proof_pubkey,
1702                    &context_state_authority_pubkey,
1703                    &ciphertext_validity_proof_data_with_ciphertext.proof_data,
1704                    false,
1705                    create_ciphertext_validity_proof_context_signer
1706                )
1707            )?;
1708
1709            // do the transfer
1710            let ciphertext_validity_proof_account_with_ciphertext = ProofAccountWithCiphertext {
1711                context_state_account: ciphertext_validity_proof_pubkey,
1712                ciphertext_lo: transfer_amount_auditor_ciphertext_lo,
1713                ciphertext_hi: transfer_amount_auditor_ciphertext_hi,
1714            };
1715
1716            let transfer_result = token
1717                .confidential_transfer_transfer(
1718                    &sender,
1719                    &recipient_token_account,
1720                    &sender_owner,
1721                    Some(&equality_proof_pubkey),
1722                    Some(&ciphertext_validity_proof_account_with_ciphertext),
1723                    Some(&range_proof_pubkey),
1724                    transfer_balance,
1725                    Some(transfer_account_info),
1726                    &args.sender_elgamal_keypair,
1727                    &args.sender_aes_key,
1728                    &recipient_elgamal_pubkey,
1729                    auditor_elgamal_pubkey.as_ref(),
1730                    &bulk_signers,
1731                )
1732                .await?;
1733
1734            // close context state accounts
1735            let close_context_state_signer = &[&context_state_authority];
1736            let _ = try_join!(
1737                token.confidential_transfer_close_context_state_account(
1738                    &equality_proof_pubkey,
1739                    &sender,
1740                    &context_state_authority_pubkey,
1741                    close_context_state_signer
1742                ),
1743                token.confidential_transfer_close_context_state_account(
1744                    &ciphertext_validity_proof_pubkey,
1745                    &sender,
1746                    &context_state_authority_pubkey,
1747                    close_context_state_signer
1748                ),
1749                token.confidential_transfer_close_context_state_account(
1750                    &range_proof_pubkey,
1751                    &sender,
1752                    &context_state_authority_pubkey,
1753                    close_context_state_signer
1754                ),
1755            )?;
1756
1757            transfer_result
1758        }
1759        (None, Some(_), Some(_)) => {
1760            panic!("Confidential transfer with fee is not yet supported.");
1761        }
1762        (None, None, None) => {
1763            token
1764                .transfer(
1765                    &sender,
1766                    &recipient_token_account,
1767                    &sender_owner,
1768                    transfer_balance,
1769                    &bulk_signers,
1770                )
1771                .await?
1772        }
1773    };
1774
1775    let tx_return = finish_tx(config, &res, no_wait).await?;
1776    Ok(match tx_return {
1777        TransactionReturnData::CliSignature(signature) => {
1778            config.output_format.formatted_string(&signature)
1779        }
1780        TransactionReturnData::CliSignOnlyData(sign_only_data) => {
1781            config.output_format.formatted_string(&sign_only_data)
1782        }
1783    })
1784}
1785
1786#[allow(clippy::too_many_arguments)]
1787async fn command_burn(
1788    config: &Config<'_>,
1789    account: Pubkey,
1790    owner: Pubkey,
1791    ui_amount: Amount,
1792    mint_address: Option<Pubkey>,
1793    mint_decimals: Option<u8>,
1794    use_unchecked_instruction: bool,
1795    memo: Option<String>,
1796    bulk_signers: BulkSigners,
1797) -> CommandResult {
1798    let mint_address = config.check_account(&account, mint_address).await?;
1799    let mint_info = config.get_mint_info(&mint_address, mint_decimals).await?;
1800    let decimals = if use_unchecked_instruction {
1801        None
1802    } else {
1803        Some(mint_info.decimals)
1804    };
1805
1806    let token = token_client_from_config(config, &mint_info.address, decimals)?;
1807
1808    let amount = match ui_amount {
1809        Amount::Raw(ui_amount) => ui_amount,
1810        Amount::Decimal(ui_amount) => {
1811            spl_token_2022::ui_amount_to_amount(ui_amount, mint_info.decimals)
1812        }
1813        Amount::All => {
1814            if config.sign_only {
1815                return Err("Use of ALL keyword to burn tokens requires online signing"
1816                    .to_string()
1817                    .into());
1818            }
1819            token.get_account_info(&account).await?.base.amount
1820        }
1821    };
1822
1823    println_display(
1824        config,
1825        format!(
1826            "Burn {} tokens\n  Source: {}",
1827            spl_token_2022::amount_to_ui_amount(amount, mint_info.decimals),
1828            account
1829        ),
1830    );
1831
1832    if let Some(text) = memo {
1833        token.with_memo(text, vec![config.default_signer()?.pubkey()]);
1834    }
1835
1836    let res = token.burn(&account, &owner, amount, &bulk_signers).await?;
1837
1838    let tx_return = finish_tx(config, &res, false).await?;
1839    Ok(match tx_return {
1840        TransactionReturnData::CliSignature(signature) => {
1841            config.output_format.formatted_string(&signature)
1842        }
1843        TransactionReturnData::CliSignOnlyData(sign_only_data) => {
1844            config.output_format.formatted_string(&sign_only_data)
1845        }
1846    })
1847}
1848
1849#[allow(clippy::too_many_arguments)]
1850async fn command_mint(
1851    config: &Config<'_>,
1852    token: Pubkey,
1853    ui_amount: Amount,
1854    recipient: Pubkey,
1855    mint_info: MintInfo,
1856    mint_authority: Pubkey,
1857    use_unchecked_instruction: bool,
1858    memo: Option<String>,
1859    bulk_signers: BulkSigners,
1860) -> CommandResult {
1861    let amount = amount_to_raw_amount(ui_amount, mint_info.decimals, None, "TOKEN_AMOUNT");
1862
1863    println_display(
1864        config,
1865        format!(
1866            "Minting {} tokens\n  Token: {}\n  Recipient: {}",
1867            spl_token_2022::amount_to_ui_amount(amount, mint_info.decimals),
1868            token,
1869            recipient
1870        ),
1871    );
1872
1873    let decimals = if use_unchecked_instruction {
1874        None
1875    } else {
1876        Some(mint_info.decimals)
1877    };
1878
1879    let token = token_client_from_config(config, &mint_info.address, decimals)?;
1880    if let Some(text) = memo {
1881        token.with_memo(text, vec![config.default_signer()?.pubkey()]);
1882    }
1883
1884    let res = token
1885        .mint_to(&recipient, &mint_authority, amount, &bulk_signers)
1886        .await?;
1887
1888    let tx_return = finish_tx(config, &res, false).await?;
1889    Ok(match tx_return {
1890        TransactionReturnData::CliSignature(signature) => {
1891            config.output_format.formatted_string(&signature)
1892        }
1893        TransactionReturnData::CliSignOnlyData(sign_only_data) => {
1894            config.output_format.formatted_string(&sign_only_data)
1895        }
1896    })
1897}
1898
1899async fn command_freeze(
1900    config: &Config<'_>,
1901    account: Pubkey,
1902    mint_address: Option<Pubkey>,
1903    freeze_authority: Pubkey,
1904    bulk_signers: BulkSigners,
1905) -> CommandResult {
1906    let mint_address = config.check_account(&account, mint_address).await?;
1907    let mint_info = config.get_mint_info(&mint_address, None).await?;
1908
1909    println_display(
1910        config,
1911        format!(
1912            "Freezing account: {}\n  Token: {}",
1913            account, mint_info.address
1914        ),
1915    );
1916
1917    // we dont use the decimals from mint_info because its not need and in sign-only
1918    // its wrong
1919    let token = token_client_from_config(config, &mint_info.address, None)?;
1920    let res = token
1921        .freeze(&account, &freeze_authority, &bulk_signers)
1922        .await?;
1923
1924    let tx_return = finish_tx(config, &res, false).await?;
1925    Ok(match tx_return {
1926        TransactionReturnData::CliSignature(signature) => {
1927            config.output_format.formatted_string(&signature)
1928        }
1929        TransactionReturnData::CliSignOnlyData(sign_only_data) => {
1930            config.output_format.formatted_string(&sign_only_data)
1931        }
1932    })
1933}
1934
1935async fn command_thaw(
1936    config: &Config<'_>,
1937    account: Pubkey,
1938    mint_address: Option<Pubkey>,
1939    freeze_authority: Pubkey,
1940    bulk_signers: BulkSigners,
1941) -> CommandResult {
1942    let mint_address = config.check_account(&account, mint_address).await?;
1943    let mint_info = config.get_mint_info(&mint_address, None).await?;
1944
1945    println_display(
1946        config,
1947        format!(
1948            "Thawing account: {}\n  Token: {}",
1949            account, mint_info.address
1950        ),
1951    );
1952
1953    // we dont use the decimals from mint_info because its not need and in sign-only
1954    // its wrong
1955    let token = token_client_from_config(config, &mint_info.address, None)?;
1956    let res = token
1957        .thaw(&account, &freeze_authority, &bulk_signers)
1958        .await?;
1959
1960    let tx_return = finish_tx(config, &res, false).await?;
1961    Ok(match tx_return {
1962        TransactionReturnData::CliSignature(signature) => {
1963            config.output_format.formatted_string(&signature)
1964        }
1965        TransactionReturnData::CliSignOnlyData(sign_only_data) => {
1966            config.output_format.formatted_string(&sign_only_data)
1967        }
1968    })
1969}
1970
1971async fn command_wrap(
1972    config: &Config<'_>,
1973    amount: Amount,
1974    wallet_address: Pubkey,
1975    wrapped_sol_account: Option<Pubkey>,
1976    immutable_owner: bool,
1977    bulk_signers: BulkSigners,
1978) -> CommandResult {
1979    let lamports = match amount.sol_to_lamport() {
1980        Amount::All => {
1981            return Err("ALL keyword not supported for SOL amount".into());
1982        }
1983        Amount::Raw(amount) => amount,
1984        Amount::Decimal(_) => {
1985            unreachable!();
1986        }
1987    };
1988    let token = native_token_client_from_config(config)?;
1989
1990    let account =
1991        wrapped_sol_account.unwrap_or_else(|| token.get_associated_token_address(&wallet_address));
1992
1993    println_display(
1994        config,
1995        format!(
1996            "Wrapping {} SOL into {}",
1997            build_balance_message(lamports, false, false),
1998            account
1999        ),
2000    );
2001
2002    if !config.sign_only {
2003        if let Some(account_data) = config.program_client.get_account(account).await? {
2004            if account_data.owner != system_program::id() {
2005                return Err(format!("Error: Account already exists: {}", account).into());
2006            }
2007        }
2008
2009        check_wallet_balance(config, &wallet_address, lamports).await?;
2010    }
2011
2012    let res = if immutable_owner {
2013        if config.program_id == spl_token_interface::id() {
2014            return Err(format!(
2015                "Specified --immutable, but token program {} does not support the extension",
2016                config.program_id
2017            )
2018            .into());
2019        }
2020
2021        token
2022            .wrap(&account, &wallet_address, lamports, &bulk_signers)
2023            .await?
2024    } else {
2025        // this case is hit for a token22 ata, which is always immutable. but it does
2026        // the right thing anyway
2027        token
2028            .wrap_with_mutable_ownership(&account, &wallet_address, lamports, &bulk_signers)
2029            .await?
2030    };
2031
2032    let tx_return = finish_tx(config, &res, false).await?;
2033    Ok(match tx_return {
2034        TransactionReturnData::CliSignature(signature) => {
2035            config.output_format.formatted_string(&signature)
2036        }
2037        TransactionReturnData::CliSignOnlyData(sign_only_data) => {
2038            config.output_format.formatted_string(&sign_only_data)
2039        }
2040    })
2041}
2042
2043async fn command_unwrap(
2044    config: &Config<'_>,
2045    wallet_address: Pubkey,
2046    maybe_account: Option<Pubkey>,
2047    bulk_signers: BulkSigners,
2048) -> CommandResult {
2049    let use_associated_account = maybe_account.is_none();
2050    let token = native_token_client_from_config(config)?;
2051
2052    let account =
2053        maybe_account.unwrap_or_else(|| token.get_associated_token_address(&wallet_address));
2054
2055    println_display(config, format!("Unwrapping {}", account));
2056
2057    if !config.sign_only {
2058        let account_data = config.get_account_checked(&account).await?;
2059
2060        if !use_associated_account {
2061            let account_state = StateWithExtensionsOwned::<Account>::unpack(account_data.data)?;
2062
2063            if account_state.base.mint != *token.get_address() {
2064                return Err(format!("{} is not a native token account", account).into());
2065            }
2066        }
2067
2068        if account_data.lamports == 0 {
2069            if use_associated_account {
2070                return Err("No wrapped SOL in associated account; did you mean to specify an auxiliary address?".to_string().into());
2071            } else {
2072                return Err(format!("No wrapped SOL in {}", account).into());
2073            }
2074        }
2075
2076        println_display(
2077            config,
2078            format!(
2079                "  Amount: {} SOL",
2080                build_balance_message(account_data.lamports, false, false)
2081            ),
2082        );
2083    }
2084
2085    println_display(config, format!("  Recipient: {}", &wallet_address));
2086
2087    let res = token
2088        .close_account(&account, &wallet_address, &wallet_address, &bulk_signers)
2089        .await?;
2090
2091    let tx_return = finish_tx(config, &res, false).await?;
2092    Ok(match tx_return {
2093        TransactionReturnData::CliSignature(signature) => {
2094            config.output_format.formatted_string(&signature)
2095        }
2096        TransactionReturnData::CliSignOnlyData(sign_only_data) => {
2097            config.output_format.formatted_string(&sign_only_data)
2098        }
2099    })
2100}
2101
2102#[allow(clippy::too_many_arguments)]
2103async fn command_approve(
2104    config: &Config<'_>,
2105    account: Pubkey,
2106    owner: Pubkey,
2107    ui_amount: Amount,
2108    delegate: Pubkey,
2109    mint_address: Option<Pubkey>,
2110    mint_decimals: Option<u8>,
2111    use_unchecked_instruction: bool,
2112    bulk_signers: BulkSigners,
2113) -> CommandResult {
2114    let mint_address = config.check_account(&account, mint_address).await?;
2115    let mint_info = config.get_mint_info(&mint_address, mint_decimals).await?;
2116    let amount = amount_to_raw_amount(ui_amount, mint_info.decimals, None, "TOKEN_AMOUNT");
2117    let decimals = if use_unchecked_instruction {
2118        None
2119    } else {
2120        Some(mint_info.decimals)
2121    };
2122
2123    println_display(
2124        config,
2125        format!(
2126            "Approve {} tokens\n  Account: {}\n  Delegate: {}",
2127            spl_token_2022::amount_to_ui_amount(amount, mint_info.decimals),
2128            account,
2129            delegate
2130        ),
2131    );
2132
2133    let token = token_client_from_config(config, &mint_info.address, decimals)?;
2134    let res = token
2135        .approve(&account, &delegate, &owner, amount, &bulk_signers)
2136        .await?;
2137
2138    let tx_return = finish_tx(config, &res, false).await?;
2139    Ok(match tx_return {
2140        TransactionReturnData::CliSignature(signature) => {
2141            config.output_format.formatted_string(&signature)
2142        }
2143        TransactionReturnData::CliSignOnlyData(sign_only_data) => {
2144            config.output_format.formatted_string(&sign_only_data)
2145        }
2146    })
2147}
2148
2149async fn command_revoke(
2150    config: &Config<'_>,
2151    account: Pubkey,
2152    owner: Pubkey,
2153    delegate: Option<Pubkey>,
2154    bulk_signers: BulkSigners,
2155) -> CommandResult {
2156    let (mint_pubkey, delegate) = if !config.sign_only {
2157        let source_account = config.get_account_checked(&account).await?;
2158        let source_state = StateWithExtensionsOwned::<Account>::unpack(source_account.data)
2159            .map_err(|_| format!("Could not deserialize token account {}", account))?;
2160
2161        let delegate = if let COption::Some(delegate) = source_state.base.delegate {
2162            Some(delegate)
2163        } else {
2164            None
2165        };
2166
2167        (source_state.base.mint, delegate)
2168    } else {
2169        // default is safe here because revoke doesn't use it
2170        (Pubkey::default(), delegate)
2171    };
2172
2173    if let Some(delegate) = delegate {
2174        println_display(
2175            config,
2176            format!(
2177                "Revoking approval\n  Account: {}\n  Delegate: {}",
2178                account, delegate
2179            ),
2180        );
2181    } else {
2182        return Err(format!("No delegate on account {}", account).into());
2183    }
2184
2185    let token = token_client_from_config(config, &mint_pubkey, None)?;
2186    let res = token.revoke(&account, &owner, &bulk_signers).await?;
2187
2188    let tx_return = finish_tx(config, &res, false).await?;
2189    Ok(match tx_return {
2190        TransactionReturnData::CliSignature(signature) => {
2191            config.output_format.formatted_string(&signature)
2192        }
2193        TransactionReturnData::CliSignOnlyData(sign_only_data) => {
2194            config.output_format.formatted_string(&sign_only_data)
2195        }
2196    })
2197}
2198
2199async fn command_close(
2200    config: &Config<'_>,
2201    account: Pubkey,
2202    close_authority: Pubkey,
2203    recipient: Pubkey,
2204    bulk_signers: BulkSigners,
2205) -> CommandResult {
2206    let mut results = vec![];
2207    let token = if !config.sign_only {
2208        let source_account = config.get_account_checked(&account).await?;
2209
2210        let source_state = StateWithExtensionsOwned::<Account>::unpack(source_account.data)
2211            .map_err(|_| format!("Could not deserialize token account {}", account))?;
2212        let source_amount = source_state.base.amount;
2213
2214        if !source_state.base.is_native() && source_amount > 0 {
2215            return Err(format!(
2216                "Account {} still has {} tokens; empty the account in order to close it.",
2217                account, source_amount,
2218            )
2219            .into());
2220        }
2221
2222        let token = token_client_from_config(config, &source_state.base.mint, None)?;
2223        if let Ok(extension) = source_state.get_extension::<TransferFeeAmount>() {
2224            if u64::from(extension.withheld_amount) != 0 {
2225                let res = token.harvest_withheld_tokens_to_mint(&[&account]).await?;
2226                let tx_return = finish_tx(config, &res, false).await?;
2227                results.push(match tx_return {
2228                    TransactionReturnData::CliSignature(signature) => {
2229                        config.output_format.formatted_string(&signature)
2230                    }
2231                    TransactionReturnData::CliSignOnlyData(sign_only_data) => {
2232                        config.output_format.formatted_string(&sign_only_data)
2233                    }
2234                });
2235            }
2236        }
2237
2238        token
2239    } else {
2240        // default is safe here because close doesn't use it
2241        token_client_from_config(config, &Pubkey::default(), None)?
2242    };
2243
2244    let res = token
2245        .close_account(&account, &recipient, &close_authority, &bulk_signers)
2246        .await?;
2247
2248    let tx_return = finish_tx(config, &res, false).await?;
2249    results.push(match tx_return {
2250        TransactionReturnData::CliSignature(signature) => {
2251            config.output_format.formatted_string(&signature)
2252        }
2253        TransactionReturnData::CliSignOnlyData(sign_only_data) => {
2254            config.output_format.formatted_string(&sign_only_data)
2255        }
2256    });
2257    Ok(results.join(""))
2258}
2259
2260async fn command_close_mint(
2261    config: &Config<'_>,
2262    token_pubkey: Pubkey,
2263    close_authority: Pubkey,
2264    recipient: Pubkey,
2265    bulk_signers: BulkSigners,
2266) -> CommandResult {
2267    if !config.sign_only {
2268        let mint_account = config.get_account_checked(&token_pubkey).await?;
2269
2270        let mint_state = StateWithExtensionsOwned::<Mint>::unpack(mint_account.data)
2271            .map_err(|_| format!("Could not deserialize token mint {}", token_pubkey))?;
2272        let mint_supply = mint_state.base.supply;
2273
2274        if mint_supply > 0 {
2275            return Err(format!(
2276                "Mint {} still has {} outstanding tokens; these must be burned before closing the mint.",
2277                token_pubkey, mint_supply,
2278            )
2279            .into());
2280        }
2281
2282        if let Ok(mint_close_authority) = mint_state.get_extension::<MintCloseAuthority>() {
2283            let mint_close_authority_pubkey =
2284                Option::<Pubkey>::from(mint_close_authority.close_authority);
2285
2286            if mint_close_authority_pubkey != Some(close_authority) {
2287                return Err(format!(
2288                    "Mint {} has close authority {}, but {} was provided",
2289                    token_pubkey,
2290                    mint_close_authority_pubkey
2291                        .map(|pubkey| pubkey.to_string())
2292                        .unwrap_or_else(|| "disabled".to_string()),
2293                    close_authority
2294                )
2295                .into());
2296            }
2297        } else {
2298            return Err(format!("Mint {} does not support close authority", token_pubkey).into());
2299        }
2300    }
2301
2302    let token = token_client_from_config(config, &token_pubkey, None)?;
2303    let res = token
2304        .close_account(&token_pubkey, &recipient, &close_authority, &bulk_signers)
2305        .await?;
2306
2307    let tx_return = finish_tx(config, &res, false).await?;
2308    Ok(match tx_return {
2309        TransactionReturnData::CliSignature(signature) => {
2310            config.output_format.formatted_string(&signature)
2311        }
2312        TransactionReturnData::CliSignOnlyData(sign_only_data) => {
2313            config.output_format.formatted_string(&sign_only_data)
2314        }
2315    })
2316}
2317
2318async fn command_balance(config: &Config<'_>, address: Pubkey) -> CommandResult {
2319    let balance = config
2320        .rpc_client
2321        .get_token_account_balance(&address)
2322        .await
2323        .map_err(|_| format!("Could not find token account {}", address))?;
2324    let cli_token_amount = CliTokenAmount { amount: balance };
2325    Ok(config.output_format.formatted_string(&cli_token_amount))
2326}
2327
2328async fn command_supply(config: &Config<'_>, token: Pubkey) -> CommandResult {
2329    let supply = config.rpc_client.get_token_supply(&token).await?;
2330    let cli_token_amount = CliTokenAmount { amount: supply };
2331    Ok(config.output_format.formatted_string(&cli_token_amount))
2332}
2333
2334async fn command_accounts(
2335    config: &Config<'_>,
2336    maybe_token: Option<Pubkey>,
2337    owner: Pubkey,
2338    account_filter: AccountFilter,
2339    print_addresses_only: bool,
2340) -> CommandResult {
2341    let filters = if let Some(token_pubkey) = maybe_token {
2342        let _ = config.get_mint_info(&token_pubkey, None).await?;
2343        vec![TokenAccountsFilter::Mint(token_pubkey)]
2344    } else if config.restrict_to_program_id {
2345        vec![TokenAccountsFilter::ProgramId(config.program_id)]
2346    } else {
2347        vec![
2348            TokenAccountsFilter::ProgramId(spl_token_interface::id()),
2349            TokenAccountsFilter::ProgramId(spl_token_2022_interface::id()),
2350        ]
2351    };
2352
2353    let mut accounts = vec![];
2354    for filter in filters {
2355        accounts.push(
2356            config
2357                .rpc_client
2358                .get_token_accounts_by_owner(&owner, filter)
2359                .await?,
2360        );
2361    }
2362    let accounts = accounts.into_iter().flatten().collect();
2363
2364    let cli_token_accounts =
2365        sort_and_parse_token_accounts(&owner, accounts, maybe_token.is_some(), account_filter)?;
2366
2367    if print_addresses_only {
2368        Ok(cli_token_accounts
2369            .accounts
2370            .into_iter()
2371            .flatten()
2372            .map(|a| a.address)
2373            .collect::<Vec<_>>()
2374            .join("\n"))
2375    } else {
2376        Ok(config.output_format.formatted_string(&cli_token_accounts))
2377    }
2378}
2379
2380async fn command_address(
2381    config: &Config<'_>,
2382    token: Option<Pubkey>,
2383    owner: Pubkey,
2384) -> CommandResult {
2385    let mut cli_address = CliWalletAddress {
2386        wallet_address: owner.to_string(),
2387        ..CliWalletAddress::default()
2388    };
2389    if let Some(token) = token {
2390        config.get_mint_info(&token, None).await?;
2391        let associated_token_address =
2392            get_associated_token_address_with_program_id(&owner, &token, &config.program_id);
2393        cli_address.associated_token_address = Some(associated_token_address.to_string());
2394    }
2395    Ok(config.output_format.formatted_string(&cli_address))
2396}
2397
2398async fn command_display(config: &Config<'_>, address: Pubkey) -> CommandResult {
2399    let account_data = config.get_account_checked(&address).await?;
2400
2401    let (additional_data, has_permanent_delegate) =
2402        if let Some(mint_address) = get_token_account_mint(&account_data.data) {
2403            let mint_account = config.get_account_checked(&mint_address).await?;
2404            let mint_state = StateWithExtensionsOwned::<Mint>::unpack(mint_account.data)
2405                .map_err(|_| format!("Could not deserialize token mint {}", mint_address))?;
2406
2407            let has_permanent_delegate =
2408                if let Ok(permanent_delegate) = mint_state.get_extension::<PermanentDelegate>() {
2409                    Option::<Pubkey>::from(permanent_delegate.delegate).is_some()
2410                } else {
2411                    false
2412                };
2413            let additional_data = SplTokenAdditionalDataV2::with_decimals(mint_state.base.decimals);
2414
2415            (Some(additional_data), has_permanent_delegate)
2416        } else {
2417            (None, false)
2418        };
2419
2420    let token_data = parse_token_v3(&account_data.data, additional_data.as_ref());
2421
2422    match token_data {
2423        Ok(TokenAccountType::Account(account)) => {
2424            let mint_address = Pubkey::from_str(&account.mint)?;
2425            let owner = Pubkey::from_str(&account.owner)?;
2426            let associated_address = get_associated_token_address_with_program_id(
2427                &owner,
2428                &mint_address,
2429                &config.program_id,
2430            );
2431
2432            let cli_output = CliTokenAccount {
2433                address: address.to_string(),
2434                program_id: config.program_id.to_string(),
2435                is_associated: associated_address == address,
2436                account,
2437                has_permanent_delegate,
2438            };
2439
2440            Ok(config.output_format.formatted_string(&cli_output))
2441        }
2442        Ok(TokenAccountType::Mint(mint)) => {
2443            let epoch_info = config.rpc_client.get_epoch_info().await?;
2444            let cli_output = CliMint {
2445                address: address.to_string(),
2446                epoch: epoch_info.epoch,
2447                program_id: config.program_id.to_string(),
2448                mint,
2449            };
2450
2451            Ok(config.output_format.formatted_string(&cli_output))
2452        }
2453        Ok(TokenAccountType::Multisig(multisig)) => {
2454            let cli_output = CliMultisig {
2455                address: address.to_string(),
2456                program_id: config.program_id.to_string(),
2457                multisig,
2458            };
2459
2460            Ok(config.output_format.formatted_string(&cli_output))
2461        }
2462        Err(e) => Err(e.into()),
2463    }
2464}
2465
2466async fn command_gc(
2467    config: &Config<'_>,
2468    owner: Pubkey,
2469    close_empty_associated_accounts: bool,
2470    bulk_signers: BulkSigners,
2471) -> CommandResult {
2472    println_display(
2473        config,
2474        format!(
2475            "Fetching token accounts associated with program {}",
2476            config.program_id
2477        ),
2478    );
2479    let accounts = config
2480        .rpc_client
2481        .get_token_accounts_by_owner(&owner, TokenAccountsFilter::ProgramId(config.program_id))
2482        .await?;
2483    if accounts.is_empty() {
2484        println_display(config, "Nothing to do".to_string());
2485        return Ok("".to_string());
2486    }
2487
2488    let mut accounts_by_token = HashMap::new();
2489
2490    for keyed_account in accounts {
2491        if let UiAccountData::Json(parsed_account) = keyed_account.account.data {
2492            if let Ok(TokenAccountType::Account(ui_token_account)) =
2493                serde_json::from_value(parsed_account.parsed)
2494            {
2495                let frozen = ui_token_account.state == UiAccountState::Frozen;
2496                let decimals = ui_token_account.token_amount.decimals;
2497
2498                let token = ui_token_account
2499                    .mint
2500                    .parse::<Pubkey>()
2501                    .unwrap_or_else(|err| panic!("Invalid mint: {}", err));
2502                let token_account = keyed_account
2503                    .pubkey
2504                    .parse::<Pubkey>()
2505                    .unwrap_or_else(|err| panic!("Invalid token account: {}", err));
2506                let token_amount = ui_token_account
2507                    .token_amount
2508                    .amount
2509                    .parse::<u64>()
2510                    .unwrap_or_else(|err| panic!("Invalid token amount: {}", err));
2511
2512                let close_authority = ui_token_account.close_authority.map_or(owner, |s| {
2513                    s.parse::<Pubkey>()
2514                        .unwrap_or_else(|err| panic!("Invalid close authority: {}", err))
2515                });
2516
2517                let entry = accounts_by_token
2518                    .entry((token, decimals))
2519                    .or_insert_with(HashMap::new);
2520                entry.insert(token_account, (token_amount, frozen, close_authority));
2521            }
2522        }
2523    }
2524
2525    let mut results = vec![];
2526    for ((token_pubkey, decimals), accounts) in accounts_by_token.into_iter() {
2527        println_display(config, format!("Processing token: {}", token_pubkey));
2528
2529        let token = token_client_from_config(config, &token_pubkey, Some(decimals))?;
2530        let total_balance: u64 = accounts.values().map(|account| account.0).sum();
2531
2532        let associated_token_account = token.get_associated_token_address(&owner);
2533        if !accounts.contains_key(&associated_token_account) && total_balance > 0 {
2534            token.create_associated_token_account(&owner).await?;
2535        }
2536
2537        for (address, (amount, frozen, close_authority)) in accounts {
2538            let is_associated = address == associated_token_account;
2539
2540            // only close the associated account if --close-empty-associated-accounts is
2541            // provided
2542            if is_associated && !close_empty_associated_accounts {
2543                continue;
2544            }
2545
2546            // never close the associated account if *any* account carries a balance
2547            if is_associated && total_balance > 0 {
2548                continue;
2549            }
2550
2551            // dont attempt to close frozen accounts
2552            if frozen {
2553                continue;
2554            }
2555
2556            if is_associated {
2557                println!("Closing associated account {}", address);
2558            }
2559
2560            // this logic is quite fiendish, but its more readable this way than if/else
2561            let maybe_res = match (close_authority == owner, is_associated, amount == 0) {
2562                // owner authority, associated or auxiliary, empty -> close
2563                (true, _, true) => Some(
2564                    token
2565                        .close_account(&address, &owner, &owner, &bulk_signers)
2566                        .await,
2567                ),
2568                // owner authority, auxiliary, nonempty -> empty and close
2569                (true, false, false) => Some(
2570                    token
2571                        .empty_and_close_account(
2572                            &address,
2573                            &owner,
2574                            &associated_token_account,
2575                            &owner,
2576                            &bulk_signers,
2577                        )
2578                        .await,
2579                ),
2580                // separate authority, auxiliary, nonempty -> transfer
2581                (false, false, false) => Some(
2582                    token
2583                        .transfer(
2584                            &address,
2585                            &associated_token_account,
2586                            &owner,
2587                            amount,
2588                            &bulk_signers,
2589                        )
2590                        .await,
2591                ),
2592                // separate authority, associated or auxiliary, empty -> print warning
2593                (false, _, true) => {
2594                    println_display(
2595                        config,
2596                        format!(
2597                            "Note: skipping {} due to separate close authority {}; \
2598                             revoke authority and rerun gc, or rerun gc with --owner",
2599                            address, close_authority
2600                        ),
2601                    );
2602                    None
2603                }
2604                // anything else, including a nonempty associated account -> unreachable
2605                (_, _, _) => unreachable!(),
2606            };
2607
2608            if let Some(res) = maybe_res {
2609                let tx_return = finish_tx(config, &res?, false).await?;
2610
2611                results.push(match tx_return {
2612                    TransactionReturnData::CliSignature(signature) => {
2613                        config.output_format.formatted_string(&signature)
2614                    }
2615                    TransactionReturnData::CliSignOnlyData(sign_only_data) => {
2616                        config.output_format.formatted_string(&sign_only_data)
2617                    }
2618                });
2619            };
2620        }
2621    }
2622
2623    Ok(results.join(""))
2624}
2625
2626async fn command_sync_native(config: &Config<'_>, native_account_address: Pubkey) -> CommandResult {
2627    let token = native_token_client_from_config(config)?;
2628
2629    if !config.sign_only {
2630        let account_data = config.get_account_checked(&native_account_address).await?;
2631        let account_state = StateWithExtensionsOwned::<Account>::unpack(account_data.data)?;
2632
2633        if account_state.base.mint != *token.get_address() {
2634            return Err(format!("{} is not a native token account", native_account_address).into());
2635        }
2636    }
2637
2638    let res = token.sync_native(&native_account_address).await?;
2639    let tx_return = finish_tx(config, &res, false).await?;
2640    Ok(match tx_return {
2641        TransactionReturnData::CliSignature(signature) => {
2642            config.output_format.formatted_string(&signature)
2643        }
2644        TransactionReturnData::CliSignOnlyData(sign_only_data) => {
2645            config.output_format.formatted_string(&sign_only_data)
2646        }
2647    })
2648}
2649
2650async fn command_withdraw_excess_lamports(
2651    config: &Config<'_>,
2652    source_account: Pubkey,
2653    destination_account: Pubkey,
2654    authority: Pubkey,
2655    bulk_signers: Vec<Arc<dyn Signer>>,
2656) -> CommandResult {
2657    // default is safe here because withdraw_excess_lamports doesn't use it
2658    let token = token_client_from_config(config, &Pubkey::default(), None)?;
2659    println_display(
2660        config,
2661        format!(
2662            "Withdrawing excess lamports\n  Sender: {}\n  Destination: {}",
2663            source_account, destination_account
2664        ),
2665    );
2666
2667    let res = token
2668        .withdraw_excess_lamports(
2669            &source_account,
2670            &destination_account,
2671            &authority,
2672            &bulk_signers,
2673        )
2674        .await?;
2675
2676    let tx_return = finish_tx(config, &res, false).await?;
2677
2678    Ok(match tx_return {
2679        TransactionReturnData::CliSignature(signature) => {
2680            config.output_format.formatted_string(&signature)
2681        }
2682        TransactionReturnData::CliSignOnlyData(sign_only_data) => {
2683            config.output_format.formatted_string(&sign_only_data)
2684        }
2685    })
2686}
2687
2688// both enables and disables required transfer memos, via enable_memos bool
2689async fn command_required_transfer_memos(
2690    config: &Config<'_>,
2691    token_account_address: Pubkey,
2692    owner: Pubkey,
2693    bulk_signers: BulkSigners,
2694    enable_memos: bool,
2695) -> CommandResult {
2696    if config.sign_only {
2697        panic!("Config can not be sign-only for enabling/disabling required transfer memos.");
2698    }
2699
2700    let account = config.get_account_checked(&token_account_address).await?;
2701    let current_account_len = account.data.len();
2702
2703    let state_with_extension = StateWithExtensionsOwned::<Account>::unpack(account.data)?;
2704    let token = token_client_from_config(config, &state_with_extension.base.mint, None)?;
2705
2706    // Reallocation (if needed)
2707    let mut existing_extensions: Vec<ExtensionType> = state_with_extension.get_extension_types()?;
2708    if existing_extensions.contains(&ExtensionType::MemoTransfer) {
2709        let extension_state = state_with_extension
2710            .get_extension::<MemoTransfer>()?
2711            .require_incoming_transfer_memos
2712            .into();
2713
2714        if extension_state == enable_memos {
2715            return Ok(format!(
2716                "Required transfer memos were already {}",
2717                if extension_state {
2718                    "enabled"
2719                } else {
2720                    "disabled"
2721                }
2722            ));
2723        }
2724    } else {
2725        existing_extensions.push(ExtensionType::MemoTransfer);
2726        let needed_account_len =
2727            ExtensionType::try_calculate_account_len::<Account>(&existing_extensions)?;
2728        if needed_account_len > current_account_len {
2729            token
2730                .reallocate(
2731                    &token_account_address,
2732                    &owner,
2733                    &[ExtensionType::MemoTransfer],
2734                    &bulk_signers,
2735                )
2736                .await?;
2737        }
2738    }
2739
2740    let res = if enable_memos {
2741        token
2742            .enable_required_transfer_memos(&token_account_address, &owner, &bulk_signers)
2743            .await
2744    } else {
2745        token
2746            .disable_required_transfer_memos(&token_account_address, &owner, &bulk_signers)
2747            .await
2748    }?;
2749
2750    let tx_return = finish_tx(config, &res, false).await?;
2751    Ok(match tx_return {
2752        TransactionReturnData::CliSignature(signature) => {
2753            config.output_format.formatted_string(&signature)
2754        }
2755        TransactionReturnData::CliSignOnlyData(sign_only_data) => {
2756            config.output_format.formatted_string(&sign_only_data)
2757        }
2758    })
2759}
2760
2761// both enables and disables cpi guard, via enable_guard bool
2762async fn command_cpi_guard(
2763    config: &Config<'_>,
2764    token_account_address: Pubkey,
2765    owner: Pubkey,
2766    bulk_signers: BulkSigners,
2767    enable_guard: bool,
2768) -> CommandResult {
2769    if config.sign_only {
2770        panic!("Config can not be sign-only for enabling/disabling required transfer memos.");
2771    }
2772
2773    let account = config.get_account_checked(&token_account_address).await?;
2774    let current_account_len = account.data.len();
2775
2776    let state_with_extension = StateWithExtensionsOwned::<Account>::unpack(account.data)?;
2777    let token = token_client_from_config(config, &state_with_extension.base.mint, None)?;
2778
2779    // reallocation (if needed)
2780    let mut existing_extensions: Vec<ExtensionType> = state_with_extension.get_extension_types()?;
2781    if existing_extensions.contains(&ExtensionType::CpiGuard) {
2782        let extension_state = state_with_extension
2783            .get_extension::<CpiGuard>()?
2784            .lock_cpi
2785            .into();
2786
2787        if extension_state == enable_guard {
2788            return Ok(format!(
2789                "CPI Guard was already {}",
2790                if extension_state {
2791                    "enabled"
2792                } else {
2793                    "disabled"
2794                }
2795            ));
2796        }
2797    } else {
2798        existing_extensions.push(ExtensionType::CpiGuard);
2799        let required_account_len =
2800            ExtensionType::try_calculate_account_len::<Account>(&existing_extensions)?;
2801        if required_account_len > current_account_len {
2802            token
2803                .reallocate(
2804                    &token_account_address,
2805                    &owner,
2806                    &[ExtensionType::CpiGuard],
2807                    &bulk_signers,
2808                )
2809                .await?;
2810        }
2811    }
2812
2813    let res = if enable_guard {
2814        token
2815            .enable_cpi_guard(&token_account_address, &owner, &bulk_signers)
2816            .await
2817    } else {
2818        token
2819            .disable_cpi_guard(&token_account_address, &owner, &bulk_signers)
2820            .await
2821    }?;
2822
2823    let tx_return = finish_tx(config, &res, false).await?;
2824    Ok(match tx_return {
2825        TransactionReturnData::CliSignature(signature) => {
2826            config.output_format.formatted_string(&signature)
2827        }
2828        TransactionReturnData::CliSignOnlyData(sign_only_data) => {
2829            config.output_format.formatted_string(&sign_only_data)
2830        }
2831    })
2832}
2833
2834async fn command_update_pointer_address(
2835    config: &Config<'_>,
2836    token_pubkey: Pubkey,
2837    authority: Pubkey,
2838    new_address: Option<Pubkey>,
2839    bulk_signers: BulkSigners,
2840    pointer: Pointer,
2841) -> CommandResult {
2842    if config.sign_only {
2843        panic!(
2844            "Config can not be sign-only for updating {} pointer address.",
2845            pointer
2846        );
2847    }
2848
2849    let token = token_client_from_config(config, &token_pubkey, None)?;
2850    let res = match pointer {
2851        Pointer::Metadata => {
2852            token
2853                .update_metadata_address(&authority, new_address, &bulk_signers)
2854                .await
2855        }
2856        Pointer::Group => {
2857            token
2858                .update_group_address(&authority, new_address, &bulk_signers)
2859                .await
2860        }
2861        Pointer::GroupMember => {
2862            token
2863                .update_group_member_address(&authority, new_address, &bulk_signers)
2864                .await
2865        }
2866    }?;
2867
2868    let tx_return = finish_tx(config, &res, false).await?;
2869    Ok(match tx_return {
2870        TransactionReturnData::CliSignature(signature) => {
2871            config.output_format.formatted_string(&signature)
2872        }
2873        TransactionReturnData::CliSignOnlyData(sign_only_data) => {
2874            config.output_format.formatted_string(&sign_only_data)
2875        }
2876    })
2877}
2878
2879async fn command_update_default_account_state(
2880    config: &Config<'_>,
2881    token_pubkey: Pubkey,
2882    freeze_authority: Pubkey,
2883    new_default_state: AccountState,
2884    bulk_signers: BulkSigners,
2885) -> CommandResult {
2886    if !config.sign_only {
2887        let mint_account = config.get_account_checked(&token_pubkey).await?;
2888
2889        let mint_state = StateWithExtensionsOwned::<Mint>::unpack(mint_account.data)
2890            .map_err(|_| format!("Could not deserialize token mint {}", token_pubkey))?;
2891        match mint_state.base.freeze_authority {
2892            COption::None => {
2893                return Err(format!("Mint {} has no freeze authority.", token_pubkey).into())
2894            }
2895            COption::Some(mint_freeze_authority) => {
2896                if mint_freeze_authority != freeze_authority {
2897                    return Err(format!(
2898                        "Mint {} has a freeze authority {}, {} provided",
2899                        token_pubkey, mint_freeze_authority, freeze_authority
2900                    )
2901                    .into());
2902                }
2903            }
2904        }
2905
2906        if let Ok(default_account_state) = mint_state.get_extension::<DefaultAccountState>() {
2907            if default_account_state.state == u8::from(new_default_state) {
2908                let state_string = match new_default_state {
2909                    AccountState::Frozen => "frozen",
2910                    AccountState::Initialized => "initialized",
2911                    _ => unreachable!(),
2912                };
2913                return Err(format!(
2914                    "Mint {} already has default account state {}",
2915                    token_pubkey, state_string
2916                )
2917                .into());
2918            }
2919        } else {
2920            return Err(format!(
2921                "Mint {} does not support default account states",
2922                token_pubkey
2923            )
2924            .into());
2925        }
2926    }
2927
2928    let token = token_client_from_config(config, &token_pubkey, None)?;
2929    let res = token
2930        .set_default_account_state(&freeze_authority, &new_default_state, &bulk_signers)
2931        .await?;
2932
2933    let tx_return = finish_tx(config, &res, false).await?;
2934    Ok(match tx_return {
2935        TransactionReturnData::CliSignature(signature) => {
2936            config.output_format.formatted_string(&signature)
2937        }
2938        TransactionReturnData::CliSignOnlyData(sign_only_data) => {
2939            config.output_format.formatted_string(&sign_only_data)
2940        }
2941    })
2942}
2943
2944async fn command_withdraw_withheld_tokens(
2945    config: &Config<'_>,
2946    destination_token_account: Pubkey,
2947    source_token_accounts: Vec<Pubkey>,
2948    authority: Pubkey,
2949    include_mint: bool,
2950    bulk_signers: BulkSigners,
2951) -> CommandResult {
2952    if config.sign_only {
2953        panic!("Config can not be sign-only for withdrawing withheld tokens.");
2954    }
2955    let destination_account = config
2956        .get_account_checked(&destination_token_account)
2957        .await?;
2958    let destination_state = StateWithExtensionsOwned::<Account>::unpack(destination_account.data)
2959        .map_err(|_| {
2960        format!(
2961            "Could not deserialize token account {}",
2962            destination_token_account
2963        )
2964    })?;
2965    let token_pubkey = destination_state.base.mint;
2966    destination_state
2967        .get_extension::<TransferFeeAmount>()
2968        .map_err(|_| format!("Token mint {} has no transfer fee configured", token_pubkey))?;
2969
2970    let token = token_client_from_config(config, &token_pubkey, None)?;
2971    let mut results = vec![];
2972    if include_mint {
2973        let res = token
2974            .withdraw_withheld_tokens_from_mint(
2975                &destination_token_account,
2976                &authority,
2977                &bulk_signers,
2978            )
2979            .await;
2980        let tx_return = finish_tx(config, &res?, false).await?;
2981        results.push(match tx_return {
2982            TransactionReturnData::CliSignature(signature) => {
2983                config.output_format.formatted_string(&signature)
2984            }
2985            TransactionReturnData::CliSignOnlyData(sign_only_data) => {
2986                config.output_format.formatted_string(&sign_only_data)
2987            }
2988        });
2989    }
2990
2991    let source_refs = source_token_accounts.iter().collect::<Vec<_>>();
2992    // this can be tweaked better, but keep it simple for now
2993    const MAX_WITHDRAWAL_ACCOUNTS: usize = 25;
2994    for sources in source_refs.chunks(MAX_WITHDRAWAL_ACCOUNTS) {
2995        let res = token
2996            .withdraw_withheld_tokens_from_accounts(
2997                &destination_token_account,
2998                &authority,
2999                sources,
3000                &bulk_signers,
3001            )
3002            .await;
3003        let tx_return = finish_tx(config, &res?, false).await?;
3004        results.push(match tx_return {
3005            TransactionReturnData::CliSignature(signature) => {
3006                config.output_format.formatted_string(&signature)
3007            }
3008            TransactionReturnData::CliSignOnlyData(sign_only_data) => {
3009                config.output_format.formatted_string(&sign_only_data)
3010            }
3011        });
3012    }
3013
3014    Ok(results.join(""))
3015}
3016
3017async fn command_update_confidential_transfer_settings(
3018    config: &Config<'_>,
3019    token_pubkey: Pubkey,
3020    authority: Pubkey,
3021    auto_approve: Option<bool>,
3022    auditor_pubkey: Option<ElGamalPubkeyOrNone>,
3023    bulk_signers: Vec<Arc<dyn Signer>>,
3024) -> CommandResult {
3025    let (new_auto_approve, new_auditor_pubkey) = if !config.sign_only {
3026        let confidential_transfer_account = config.get_account_checked(&token_pubkey).await?;
3027
3028        let mint_state =
3029            StateWithExtensionsOwned::<Mint>::unpack(confidential_transfer_account.data)
3030                .map_err(|_| format!("Could not deserialize token mint {}", token_pubkey))?;
3031
3032        if let Ok(confidential_transfer_mint) =
3033            mint_state.get_extension::<ConfidentialTransferMint>()
3034        {
3035            let expected_authority = Option::<Pubkey>::from(confidential_transfer_mint.authority);
3036
3037            if expected_authority != Some(authority) {
3038                return Err(format!(
3039                    "Mint {} has confidential transfer authority {}, but {} was provided",
3040                    token_pubkey,
3041                    expected_authority
3042                        .map(|pubkey| pubkey.to_string())
3043                        .unwrap_or_else(|| "disabled".to_string()),
3044                    authority
3045                )
3046                .into());
3047            }
3048
3049            let new_auto_approve = if let Some(auto_approve) = auto_approve {
3050                auto_approve
3051            } else {
3052                bool::from(confidential_transfer_mint.auto_approve_new_accounts)
3053            };
3054
3055            let new_auditor_pubkey = if let Some(auditor_pubkey) = auditor_pubkey {
3056                auditor_pubkey.into()
3057            } else {
3058                Option::<PodElGamalPubkey>::from(confidential_transfer_mint.auditor_elgamal_pubkey)
3059            };
3060
3061            (new_auto_approve, new_auditor_pubkey)
3062        } else {
3063            return Err(format!(
3064                "Mint {} does not support confidential transfers",
3065                token_pubkey
3066            )
3067            .into());
3068        }
3069    } else {
3070        let new_auto_approve = auto_approve.expect("The approve policy must be provided");
3071        let new_auditor_pubkey = auditor_pubkey
3072            .expect("The auditor encryption pubkey must be provided")
3073            .into();
3074
3075        (new_auto_approve, new_auditor_pubkey)
3076    };
3077
3078    println_display(
3079        config,
3080        format!(
3081            "Updating confidential transfer settings for {}:",
3082            token_pubkey,
3083        ),
3084    );
3085
3086    if auto_approve.is_some() {
3087        println_display(
3088            config,
3089            format!(
3090                "  approve policy set to {}",
3091                if new_auto_approve { "auto" } else { "manual" }
3092            ),
3093        );
3094    }
3095
3096    if auditor_pubkey.is_some() {
3097        if let Some(new_auditor_pubkey) = new_auditor_pubkey {
3098            println_display(
3099                config,
3100                format!("  auditor encryption pubkey set to {}", new_auditor_pubkey,),
3101            );
3102        } else {
3103            println_display(config, "  auditability disabled".to_string())
3104        }
3105    }
3106
3107    let token = token_client_from_config(config, &token_pubkey, None)?;
3108    let res = token
3109        .confidential_transfer_update_mint(
3110            &authority,
3111            new_auto_approve,
3112            new_auditor_pubkey,
3113            &bulk_signers,
3114        )
3115        .await?;
3116
3117    let tx_return = finish_tx(config, &res, false).await?;
3118    Ok(match tx_return {
3119        TransactionReturnData::CliSignature(signature) => {
3120            config.output_format.formatted_string(&signature)
3121        }
3122        TransactionReturnData::CliSignOnlyData(sign_only_data) => {
3123            config.output_format.formatted_string(&sign_only_data)
3124        }
3125    })
3126}
3127
3128#[allow(clippy::too_many_arguments)]
3129async fn command_configure_confidential_transfer_account(
3130    config: &Config<'_>,
3131    maybe_token: Option<Pubkey>,
3132    owner: Pubkey,
3133    maybe_account: Option<Pubkey>,
3134    maximum_credit_counter: Option<u64>,
3135    elgamal_keypair: &ElGamalKeypair,
3136    aes_key: &AeKey,
3137    bulk_signers: BulkSigners,
3138) -> CommandResult {
3139    if config.sign_only {
3140        panic!("Sign-only is not yet supported.");
3141    }
3142
3143    let token_account_address = if let Some(account) = maybe_account {
3144        account
3145    } else {
3146        let token_pubkey =
3147            maybe_token.expect("Either a valid token or account address must be provided");
3148        let token = token_client_from_config(config, &token_pubkey, None)?;
3149        token.get_associated_token_address(&owner)
3150    };
3151
3152    let account = config.get_account_checked(&token_account_address).await?;
3153    let current_account_len = account.data.len();
3154
3155    let state_with_extension = StateWithExtensionsOwned::<Account>::unpack(account.data)?;
3156    let token = token_client_from_config(config, &state_with_extension.base.mint, None)?;
3157
3158    // Reallocation (if needed)
3159    let mut existing_extensions: Vec<ExtensionType> = state_with_extension.get_extension_types()?;
3160    if !existing_extensions.contains(&ExtensionType::ConfidentialTransferAccount) {
3161        let mut extra_extensions = vec![ExtensionType::ConfidentialTransferAccount];
3162        if existing_extensions.contains(&ExtensionType::TransferFeeAmount) {
3163            extra_extensions.push(ExtensionType::ConfidentialTransferFeeAmount);
3164        }
3165        existing_extensions.extend_from_slice(&extra_extensions);
3166        let needed_account_len =
3167            ExtensionType::try_calculate_account_len::<Account>(&existing_extensions)?;
3168        if needed_account_len > current_account_len {
3169            token
3170                .reallocate(
3171                    &token_account_address,
3172                    &owner,
3173                    &extra_extensions,
3174                    &bulk_signers,
3175                )
3176                .await?;
3177        }
3178    }
3179
3180    let res = token
3181        .confidential_transfer_configure_token_account(
3182            &token_account_address,
3183            &owner,
3184            None,
3185            maximum_credit_counter,
3186            elgamal_keypair,
3187            aes_key,
3188            &bulk_signers,
3189        )
3190        .await?;
3191
3192    let tx_return = finish_tx(config, &res, false).await?;
3193    Ok(match tx_return {
3194        TransactionReturnData::CliSignature(signature) => {
3195            config.output_format.formatted_string(&signature)
3196        }
3197        TransactionReturnData::CliSignOnlyData(sign_only_data) => {
3198            config.output_format.formatted_string(&sign_only_data)
3199        }
3200    })
3201}
3202
3203async fn command_enable_disable_confidential_transfers(
3204    config: &Config<'_>,
3205    maybe_token: Option<Pubkey>,
3206    owner: Pubkey,
3207    maybe_account: Option<Pubkey>,
3208    bulk_signers: BulkSigners,
3209    allow_confidential_credits: Option<bool>,
3210    allow_non_confidential_credits: Option<bool>,
3211) -> CommandResult {
3212    if config.sign_only {
3213        panic!("Sign-only is not yet supported.");
3214    }
3215
3216    let token_account_address = if let Some(account) = maybe_account {
3217        account
3218    } else {
3219        let token_pubkey =
3220            maybe_token.expect("Either a valid token or account address must be provided");
3221        let token = token_client_from_config(config, &token_pubkey, None)?;
3222        token.get_associated_token_address(&owner)
3223    };
3224
3225    let account = config.get_account_checked(&token_account_address).await?;
3226
3227    let state_with_extension = StateWithExtensionsOwned::<Account>::unpack(account.data)?;
3228    let token = token_client_from_config(config, &state_with_extension.base.mint, None)?;
3229
3230    let existing_extensions: Vec<ExtensionType> = state_with_extension.get_extension_types()?;
3231    if !existing_extensions.contains(&ExtensionType::ConfidentialTransferAccount) {
3232        panic!(
3233            "Confidential transfer is not yet configured for this account. \
3234        Use `configure-confidential-transfer-account` command instead."
3235        );
3236    }
3237
3238    let res = if let Some(allow_confidential_credits) = allow_confidential_credits {
3239        let extension_state = state_with_extension
3240            .get_extension::<ConfidentialTransferAccount>()?
3241            .allow_confidential_credits
3242            .into();
3243
3244        if extension_state == allow_confidential_credits {
3245            return Ok(format!(
3246                "Confidential transfers are already {}",
3247                if extension_state {
3248                    "enabled"
3249                } else {
3250                    "disabled"
3251                }
3252            ));
3253        }
3254
3255        if allow_confidential_credits {
3256            token
3257                .confidential_transfer_enable_confidential_credits(
3258                    &token_account_address,
3259                    &owner,
3260                    &bulk_signers,
3261                )
3262                .await
3263        } else {
3264            token
3265                .confidential_transfer_disable_confidential_credits(
3266                    &token_account_address,
3267                    &owner,
3268                    &bulk_signers,
3269                )
3270                .await
3271        }
3272    } else {
3273        let allow_non_confidential_credits =
3274            allow_non_confidential_credits.expect("Nothing to be done");
3275        let extension_state = state_with_extension
3276            .get_extension::<ConfidentialTransferAccount>()?
3277            .allow_non_confidential_credits
3278            .into();
3279
3280        if extension_state == allow_non_confidential_credits {
3281            return Ok(format!(
3282                "Non-confidential transfers are already {}",
3283                if extension_state {
3284                    "enabled"
3285                } else {
3286                    "disabled"
3287                }
3288            ));
3289        }
3290
3291        if allow_non_confidential_credits {
3292            token
3293                .confidential_transfer_enable_non_confidential_credits(
3294                    &token_account_address,
3295                    &owner,
3296                    &bulk_signers,
3297                )
3298                .await
3299        } else {
3300            token
3301                .confidential_transfer_disable_non_confidential_credits(
3302                    &token_account_address,
3303                    &owner,
3304                    &bulk_signers,
3305                )
3306                .await
3307        }
3308    }?;
3309
3310    let tx_return = finish_tx(config, &res, false).await?;
3311    Ok(match tx_return {
3312        TransactionReturnData::CliSignature(signature) => {
3313            config.output_format.formatted_string(&signature)
3314        }
3315        TransactionReturnData::CliSignOnlyData(sign_only_data) => {
3316            config.output_format.formatted_string(&sign_only_data)
3317        }
3318    })
3319}
3320#[derive(PartialEq, Eq)]
3321enum ConfidentialInstructionType {
3322    Deposit,
3323    Withdraw,
3324}
3325
3326#[allow(clippy::too_many_arguments)]
3327async fn command_deposit_withdraw_confidential_tokens(
3328    config: &Config<'_>,
3329    token_pubkey: Pubkey,
3330    owner: Pubkey,
3331    maybe_account: Option<Pubkey>,
3332    bulk_signers: BulkSigners,
3333    ui_amount: Amount,
3334    mint_decimals: Option<u8>,
3335    instruction_type: ConfidentialInstructionType,
3336    elgamal_keypair: Option<&ElGamalKeypair>,
3337    aes_key: Option<&AeKey>,
3338) -> CommandResult {
3339    if config.sign_only {
3340        panic!("Sign-only is not yet supported.");
3341    }
3342
3343    // check if mint decimals provided is consistent
3344    let mint_info = config.get_mint_info(&token_pubkey, mint_decimals).await?;
3345
3346    if !config.sign_only && mint_decimals.is_some() && mint_decimals != Some(mint_info.decimals) {
3347        return Err(format!(
3348            "Decimals {} was provided, but actual value is {}",
3349            mint_decimals.unwrap(),
3350            mint_info.decimals
3351        )
3352        .into());
3353    }
3354
3355    let decimals = if let Some(decimals) = mint_decimals {
3356        decimals
3357    } else {
3358        mint_info.decimals
3359    };
3360
3361    // derive ATA if account address not provided
3362    let token_account_address = if let Some(account) = maybe_account {
3363        account
3364    } else {
3365        let token = token_client_from_config(config, &token_pubkey, Some(decimals))?;
3366        token.get_associated_token_address(&owner)
3367    };
3368
3369    let account = config.get_account_checked(&token_account_address).await?;
3370
3371    let state_with_extension = StateWithExtensionsOwned::<Account>::unpack(account.data)?;
3372    let token = token_client_from_config(config, &state_with_extension.base.mint, None)?;
3373
3374    // the amount the user wants to deposit or withdraw, as a u64
3375    let amount = match ui_amount {
3376        Amount::Raw(ui_amount) => ui_amount,
3377        Amount::Decimal(ui_amount) => {
3378            spl_token_2022::ui_amount_to_amount(ui_amount, mint_info.decimals)
3379        }
3380        Amount::All => {
3381            if config.sign_only {
3382                return Err("Use of ALL keyword to burn tokens requires online signing"
3383                    .to_string()
3384                    .into());
3385            }
3386            if instruction_type == ConfidentialInstructionType::Withdraw {
3387                return Err("ALL keyword is not currently supported for withdraw"
3388                    .to_string()
3389                    .into());
3390            }
3391            state_with_extension.base.amount
3392        }
3393    };
3394
3395    match instruction_type {
3396        ConfidentialInstructionType::Deposit => {
3397            println_display(
3398                config,
3399                format!(
3400                    "Depositing {} confidential tokens",
3401                    spl_token_2022::amount_to_ui_amount(amount, mint_info.decimals),
3402                ),
3403            );
3404            let current_balance = state_with_extension.base.amount;
3405            if amount > current_balance {
3406                return Err(format!(
3407                    "Error: Insufficient funds, current balance is {}",
3408                    spl_token_2022::amount_to_ui_amount_string_trimmed(
3409                        current_balance,
3410                        mint_info.decimals
3411                    )
3412                )
3413                .into());
3414            }
3415        }
3416        ConfidentialInstructionType::Withdraw => {
3417            println_display(
3418                config,
3419                format!(
3420                    "Withdrawing {} confidential tokens",
3421                    spl_token_2022::amount_to_ui_amount(amount, mint_info.decimals)
3422                ),
3423            );
3424        }
3425    }
3426
3427    let res = match instruction_type {
3428        ConfidentialInstructionType::Deposit => {
3429            token
3430                .confidential_transfer_deposit(
3431                    &token_account_address,
3432                    &owner,
3433                    amount,
3434                    decimals,
3435                    &bulk_signers,
3436                )
3437                .await?
3438        }
3439        ConfidentialInstructionType::Withdraw => {
3440            let elgamal_keypair = elgamal_keypair.expect("ElGamal keypair must be provided");
3441            let aes_key = aes_key.expect("AES key must be provided");
3442
3443            let extension_state =
3444                state_with_extension.get_extension::<ConfidentialTransferAccount>()?;
3445            let withdraw_account_info = WithdrawAccountInfo::new(extension_state);
3446
3447            let context_state_authority = config.fee_payer()?;
3448            let equality_proof_context_state_keypair = Keypair::new();
3449            let equality_proof_context_state_pubkey = equality_proof_context_state_keypair.pubkey();
3450            let range_proof_context_state_keypair = Keypair::new();
3451            let range_proof_context_state_pubkey = range_proof_context_state_keypair.pubkey();
3452
3453            let WithdrawProofData {
3454                equality_proof_data,
3455                range_proof_data,
3456            } = withdraw_account_info.generate_proof_data(amount, elgamal_keypair, aes_key)?;
3457
3458            // set up context state accounts
3459            let context_state_authority_pubkey = context_state_authority.pubkey();
3460            let create_equality_proof_signer = &[&equality_proof_context_state_keypair];
3461            let create_range_proof_signer = &[&range_proof_context_state_keypair];
3462
3463            let _ = try_join!(
3464                token.confidential_transfer_create_context_state_account(
3465                    &equality_proof_context_state_pubkey,
3466                    &context_state_authority_pubkey,
3467                    &equality_proof_data,
3468                    false,
3469                    create_equality_proof_signer
3470                ),
3471                token.confidential_transfer_create_context_state_account(
3472                    &range_proof_context_state_pubkey,
3473                    &context_state_authority_pubkey,
3474                    &range_proof_data,
3475                    true,
3476                    create_range_proof_signer,
3477                )
3478            )?;
3479
3480            // do the withdrawal
3481            let withdraw_result = token
3482                .confidential_transfer_withdraw(
3483                    &token_account_address,
3484                    &owner,
3485                    Some(&equality_proof_context_state_pubkey),
3486                    Some(&range_proof_context_state_pubkey),
3487                    amount,
3488                    decimals,
3489                    Some(withdraw_account_info),
3490                    elgamal_keypair,
3491                    aes_key,
3492                    &bulk_signers,
3493                )
3494                .await?;
3495
3496            // close context state account
3497            let close_context_state_signer = &[&context_state_authority];
3498            let _ = try_join!(
3499                token.confidential_transfer_close_context_state_account(
3500                    &equality_proof_context_state_pubkey,
3501                    &token_account_address,
3502                    &context_state_authority_pubkey,
3503                    close_context_state_signer
3504                ),
3505                token.confidential_transfer_close_context_state_account(
3506                    &range_proof_context_state_pubkey,
3507                    &token_account_address,
3508                    &context_state_authority_pubkey,
3509                    close_context_state_signer
3510                )
3511            )?;
3512
3513            withdraw_result
3514        }
3515    };
3516
3517    let tx_return = finish_tx(config, &res, false).await?;
3518    Ok(match tx_return {
3519        TransactionReturnData::CliSignature(signature) => {
3520            config.output_format.formatted_string(&signature)
3521        }
3522        TransactionReturnData::CliSignOnlyData(sign_only_data) => {
3523            config.output_format.formatted_string(&sign_only_data)
3524        }
3525    })
3526}
3527
3528#[allow(clippy::too_many_arguments)]
3529async fn command_apply_pending_balance(
3530    config: &Config<'_>,
3531    maybe_token: Option<Pubkey>,
3532    owner: Pubkey,
3533    maybe_account: Option<Pubkey>,
3534    bulk_signers: BulkSigners,
3535    elgamal_keypair: &ElGamalKeypair,
3536    aes_key: &AeKey,
3537) -> CommandResult {
3538    if config.sign_only {
3539        panic!("Sign-only is not yet supported.");
3540    }
3541
3542    // derive ATA if account address not provided
3543    let token_account_address = if let Some(account) = maybe_account {
3544        account
3545    } else {
3546        let token_pubkey =
3547            maybe_token.expect("Either a valid token or account address must be provided");
3548        let token = token_client_from_config(config, &token_pubkey, None)?;
3549        token.get_associated_token_address(&owner)
3550    };
3551
3552    let account = config.get_account_checked(&token_account_address).await?;
3553
3554    let state_with_extension = StateWithExtensionsOwned::<Account>::unpack(account.data)?;
3555    let token = token_client_from_config(config, &state_with_extension.base.mint, None)?;
3556
3557    let extension_state = state_with_extension.get_extension::<ConfidentialTransferAccount>()?;
3558    let account_info = ApplyPendingBalanceAccountInfo::new(extension_state);
3559
3560    let res = token
3561        .confidential_transfer_apply_pending_balance(
3562            &token_account_address,
3563            &owner,
3564            Some(account_info),
3565            elgamal_keypair.secret(),
3566            aes_key,
3567            &bulk_signers,
3568        )
3569        .await?;
3570
3571    let tx_return = finish_tx(config, &res, false).await?;
3572    Ok(match tx_return {
3573        TransactionReturnData::CliSignature(signature) => {
3574            config.output_format.formatted_string(&signature)
3575        }
3576        TransactionReturnData::CliSignOnlyData(sign_only_data) => {
3577            config.output_format.formatted_string(&sign_only_data)
3578        }
3579    })
3580}
3581
3582async fn command_update_multiplier(
3583    config: &Config<'_>,
3584    token_pubkey: Pubkey,
3585    ui_multiplier_authority: Pubkey,
3586    new_multiplier: f64,
3587    new_multiplier_effective_timestamp: i64,
3588    bulk_signers: Vec<Arc<dyn Signer>>,
3589) -> CommandResult {
3590    let token = token_client_from_config(config, &token_pubkey, None)?;
3591
3592    if !config.sign_only {
3593        let mint_account = config.get_account_checked(&token_pubkey).await?;
3594
3595        let mint_state = StateWithExtensionsOwned::<Mint>::unpack(mint_account.data)
3596            .map_err(|_| format!("Could not deserialize token mint {}", token_pubkey))?;
3597
3598        if let Ok(scaled_ui_amount_config) = mint_state.get_extension::<ScaledUiAmountConfig>() {
3599            let scaled_ui_amount_authority_pubkey =
3600                Option::<Pubkey>::from(scaled_ui_amount_config.authority);
3601
3602            if scaled_ui_amount_authority_pubkey != Some(ui_multiplier_authority) {
3603                return Err(format!(
3604                    "Mint {} has multiplier authority {}, but {} was provided",
3605                    token_pubkey,
3606                    scaled_ui_amount_authority_pubkey
3607                        .map(|pubkey| pubkey.to_string())
3608                        .unwrap_or_else(|| "disabled".to_string()),
3609                    ui_multiplier_authority
3610                )
3611                .into());
3612            }
3613        } else {
3614            return Err(format!("Mint {} does not have a UI multiplier", token_pubkey).into());
3615        }
3616    }
3617
3618    println_display(
3619        config,
3620        format!(
3621            "Setting UI Multiplier for {} to {} at UNIX timestamp {}",
3622            token_pubkey, new_multiplier, new_multiplier_effective_timestamp
3623        ),
3624    );
3625
3626    let res = token
3627        .update_multiplier(
3628            &ui_multiplier_authority,
3629            new_multiplier,
3630            new_multiplier_effective_timestamp,
3631            &bulk_signers,
3632        )
3633        .await?;
3634
3635    let tx_return = finish_tx(config, &res, false).await?;
3636    Ok(match tx_return {
3637        TransactionReturnData::CliSignature(signature) => {
3638            config.output_format.formatted_string(&signature)
3639        }
3640        TransactionReturnData::CliSignOnlyData(sign_only_data) => {
3641            config.output_format.formatted_string(&sign_only_data)
3642        }
3643    })
3644}
3645
3646async fn command_pause_resume(
3647    config: &Config<'_>,
3648    token_pubkey: Pubkey,
3649    pause_authority: Pubkey,
3650    bulk_signers: Vec<Arc<dyn Signer>>,
3651    allow_mint_burn_transfer: bool,
3652) -> CommandResult {
3653    if !config.sign_only {
3654        let mint_account = config.get_account_checked(&token_pubkey).await?;
3655
3656        let mint_state = StateWithExtensionsOwned::<Mint>::unpack(mint_account.data)
3657            .map_err(|_| format!("Could not deserialize token mint {}", token_pubkey))?;
3658
3659        if let Ok(pausable_config) = mint_state.get_extension::<PausableConfig>() {
3660            let pause_authority_pubkey = Option::<Pubkey>::from(pausable_config.authority);
3661
3662            if pause_authority_pubkey != Some(pause_authority) {
3663                return Err(format!(
3664                    "Mint {} has pause authority {}, but {} was provided",
3665                    token_pubkey,
3666                    pause_authority_pubkey
3667                        .map(|pubkey| pubkey.to_string())
3668                        .unwrap_or_else(|| "disabled".to_string()),
3669                    pause_authority
3670                )
3671                .into());
3672            }
3673        } else {
3674            return Err(format!("Mint {} is not pausable", token_pubkey).into());
3675        }
3676    }
3677
3678    let res = if allow_mint_burn_transfer {
3679        println_display(
3680            config,
3681            format!("Resuming mint, burn, and transfer for {}", token_pubkey,),
3682        );
3683
3684        let token = token_client_from_config(config, &token_pubkey, None)?;
3685        token.resume(&pause_authority, &bulk_signers).await?
3686    } else {
3687        println_display(
3688            config,
3689            format!("Pausing mint, burn, and transfer for {}", token_pubkey,),
3690        );
3691
3692        let token = token_client_from_config(config, &token_pubkey, None)?;
3693        token.pause(&pause_authority, &bulk_signers).await?
3694    };
3695
3696    let tx_return = finish_tx(config, &res, false).await?;
3697    Ok(match tx_return {
3698        TransactionReturnData::CliSignature(signature) => {
3699            config.output_format.formatted_string(&signature)
3700        }
3701        TransactionReturnData::CliSignOnlyData(sign_only_data) => {
3702            config.output_format.formatted_string(&sign_only_data)
3703        }
3704    })
3705}
3706
3707struct ConfidentialTransferArgs {
3708    sender_elgamal_keypair: ElGamalKeypair,
3709    sender_aes_key: AeKey,
3710    recipient_elgamal_pubkey: Option<PodElGamalPubkey>,
3711    auditor_elgamal_pubkey: Option<PodElGamalPubkey>,
3712}
3713
3714pub async fn process_command(
3715    sub_command: &CommandName,
3716    sub_matches: &ArgMatches,
3717    config: &Config<'_>,
3718    mut wallet_manager: Option<Rc<RemoteWalletManager>>,
3719    mut bulk_signers: Vec<Arc<dyn Signer>>,
3720) -> CommandResult {
3721    match (sub_command, sub_matches) {
3722        (CommandName::Bench, arg_matches) => {
3723            bench_process_command(
3724                arg_matches,
3725                config,
3726                std::mem::take(&mut bulk_signers),
3727                &mut wallet_manager,
3728            )
3729            .await
3730        }
3731        (CommandName::CreateToken, arg_matches) => {
3732            let decimals = *arg_matches.get_one::<u8>("decimals").unwrap();
3733            let mint_authority =
3734                config.pubkey_or_default(arg_matches, "mint_authority", &mut wallet_manager)?;
3735            let memo = value_t!(arg_matches, "memo", String).ok();
3736            let rate_bps = value_t!(arg_matches, "interest_rate", i16).ok();
3737            let metadata_address = value_t!(arg_matches, "metadata_address", Pubkey).ok();
3738            let group_address = value_t!(arg_matches, "group_address", Pubkey).ok();
3739            let member_address = value_t!(arg_matches, "member_address", Pubkey).ok();
3740            let ui_multiplier = value_t!(arg_matches, "ui_amount_multiplier", f64).ok();
3741
3742            let transfer_fee = arg_matches.values_of("transfer_fee").map(|mut v| {
3743                println_display(config,"transfer-fee has been deprecated and will be removed in a future release. Please specify --transfer-fee-basis-points and --transfer-fee-maximum-fee with a UI amount".to_string());
3744                (
3745                    v.next()
3746                        .unwrap()
3747                        .parse::<u16>()
3748                        .unwrap_or_else(print_error_and_exit),
3749                    v.next()
3750                        .unwrap()
3751                        .parse::<u64>()
3752                        .unwrap_or_else(print_error_and_exit),
3753                )
3754            });
3755
3756            let transfer_fee_basis_point = arg_matches.get_one::<u16>("transfer_fee_basis_points");
3757            let transfer_fee_maximum_fee = arg_matches
3758                .get_one::<Amount>("transfer_fee_maximum_fee")
3759                .map(|v| amount_to_raw_amount(*v, decimals, None, "MAXIMUM_FEE"));
3760            let transfer_fee = transfer_fee_basis_point
3761                .map(|v| (*v, transfer_fee_maximum_fee.unwrap()))
3762                .or(transfer_fee);
3763
3764            let (token_signer, token) =
3765                get_signer(arg_matches, "token_keypair", &mut wallet_manager)
3766                    .unwrap_or_else(new_throwaway_signer);
3767            push_signer_with_dedup(token_signer, &mut bulk_signers);
3768            let default_account_state =
3769                arg_matches
3770                    .value_of("default_account_state")
3771                    .map(|s| match s {
3772                        "initialized" => AccountState::Initialized,
3773                        "frozen" => AccountState::Frozen,
3774                        _ => unreachable!(),
3775                    });
3776            let transfer_hook_program_id =
3777                pubkey_of_signer(arg_matches, "transfer_hook", &mut wallet_manager).unwrap();
3778
3779            let confidential_transfer_auto_approve = arg_matches
3780                .value_of("enable_confidential_transfers")
3781                .map(|b| b == "auto");
3782
3783            command_create_token(
3784                config,
3785                decimals,
3786                token,
3787                mint_authority,
3788                arg_matches.is_present("enable_freeze"),
3789                arg_matches.is_present("enable_close"),
3790                arg_matches.is_present("enable_non_transferable"),
3791                arg_matches.is_present("enable_permanent_delegate"),
3792                memo,
3793                metadata_address,
3794                group_address,
3795                member_address,
3796                rate_bps,
3797                default_account_state,
3798                transfer_fee,
3799                confidential_transfer_auto_approve,
3800                transfer_hook_program_id,
3801                arg_matches.is_present("enable_metadata"),
3802                arg_matches.is_present("enable_group"),
3803                arg_matches.is_present("enable_member"),
3804                arg_matches.is_present("enable_transfer_hook"),
3805                ui_multiplier,
3806                arg_matches.is_present("enable_pause"),
3807                bulk_signers,
3808            )
3809            .await
3810        }
3811        (CommandName::SetInterestRate, arg_matches) => {
3812            let token_pubkey = pubkey_of_signer(arg_matches, "token", &mut wallet_manager)
3813                .unwrap()
3814                .unwrap();
3815            let rate_bps = value_t_or_exit!(arg_matches, "rate", i16);
3816            let (rate_authority_signer, rate_authority_pubkey) =
3817                config.signer_or_default(arg_matches, "rate_authority", &mut wallet_manager);
3818            let bulk_signers = vec![rate_authority_signer];
3819
3820            command_set_interest_rate(
3821                config,
3822                token_pubkey,
3823                rate_authority_pubkey,
3824                rate_bps,
3825                bulk_signers,
3826            )
3827            .await
3828        }
3829        (CommandName::SetTransferHook, arg_matches) => {
3830            let token_pubkey = pubkey_of_signer(arg_matches, "token", &mut wallet_manager)
3831                .unwrap()
3832                .unwrap();
3833            let new_program_id =
3834                pubkey_of_signer(arg_matches, "new_program_id", &mut wallet_manager).unwrap();
3835            let (authority_signer, authority_pubkey) =
3836                config.signer_or_default(arg_matches, "authority", &mut wallet_manager);
3837            let bulk_signers = vec![authority_signer];
3838
3839            command_set_transfer_hook_program(
3840                config,
3841                token_pubkey,
3842                authority_pubkey,
3843                new_program_id,
3844                bulk_signers,
3845            )
3846            .await
3847        }
3848        (CommandName::InitializeMetadata, arg_matches) => {
3849            let token_pubkey = pubkey_of_signer(arg_matches, "token", &mut wallet_manager)
3850                .unwrap()
3851                .unwrap();
3852            let name = arg_matches.value_of("name").unwrap().to_string();
3853            let symbol = arg_matches.value_of("symbol").unwrap().to_string();
3854            let uri = arg_matches.value_of("uri").unwrap().to_string();
3855            let (mint_authority_signer, mint_authority) =
3856                config.signer_or_default(arg_matches, "mint_authority", &mut wallet_manager);
3857            let bulk_signers = vec![mint_authority_signer];
3858            let update_authority =
3859                config.pubkey_or_default(arg_matches, "update_authority", &mut wallet_manager)?;
3860
3861            command_initialize_metadata(
3862                config,
3863                token_pubkey,
3864                update_authority,
3865                mint_authority,
3866                name,
3867                symbol,
3868                uri,
3869                bulk_signers,
3870            )
3871            .await
3872        }
3873        (CommandName::UpdateMetadata, arg_matches) => {
3874            let token_pubkey = pubkey_of_signer(arg_matches, "token", &mut wallet_manager)
3875                .unwrap()
3876                .unwrap();
3877            let (authority_signer, authority) =
3878                config.signer_or_default(arg_matches, "authority", &mut wallet_manager);
3879            let field = arg_matches.value_of("field").unwrap();
3880            let field = match field.to_lowercase().as_str() {
3881                "name" => Field::Name,
3882                "symbol" => Field::Symbol,
3883                "uri" => Field::Uri,
3884                _ => Field::Key(field.to_string()),
3885            };
3886            let value = arg_matches.value_of("value").map(|v| v.to_string());
3887            let transfer_lamports = arg_matches
3888                .get_one::<u64>(TRANSFER_LAMPORTS_ARG.name)
3889                .copied();
3890            let bulk_signers = vec![authority_signer];
3891
3892            command_update_metadata(
3893                config,
3894                token_pubkey,
3895                authority,
3896                field,
3897                value,
3898                transfer_lamports,
3899                bulk_signers,
3900            )
3901            .await
3902        }
3903        (CommandName::InitializeGroup, arg_matches) => {
3904            let token_pubkey = pubkey_of_signer(arg_matches, "token", &mut wallet_manager)
3905                .unwrap()
3906                .unwrap();
3907            let max_size = *arg_matches.get_one::<u64>("max_size").unwrap();
3908            let (mint_authority_signer, mint_authority) =
3909                config.signer_or_default(arg_matches, "mint_authority", &mut wallet_manager);
3910            let update_authority =
3911                config.pubkey_or_default(arg_matches, "update_authority", &mut wallet_manager)?;
3912            let bulk_signers = vec![mint_authority_signer];
3913
3914            command_initialize_group(
3915                config,
3916                token_pubkey,
3917                mint_authority,
3918                update_authority,
3919                max_size,
3920                bulk_signers,
3921            )
3922            .await
3923        }
3924        (CommandName::UpdateGroupMaxSize, arg_matches) => {
3925            let token_pubkey = pubkey_of_signer(arg_matches, "token", &mut wallet_manager)
3926                .unwrap()
3927                .unwrap();
3928            let new_max_size = *arg_matches.get_one::<u64>("new_max_size").unwrap();
3929            let (update_authority_signer, update_authority) =
3930                config.signer_or_default(arg_matches, "update_authority", &mut wallet_manager);
3931            let bulk_signers = vec![update_authority_signer];
3932
3933            command_update_group_max_size(
3934                config,
3935                token_pubkey,
3936                update_authority,
3937                new_max_size,
3938                bulk_signers,
3939            )
3940            .await
3941        }
3942        (CommandName::InitializeMember, arg_matches) => {
3943            let member_token_pubkey = pubkey_of_signer(arg_matches, "token", &mut wallet_manager)
3944                .unwrap()
3945                .unwrap();
3946            let group_token_pubkey =
3947                pubkey_of_signer(arg_matches, "group_token", &mut wallet_manager)
3948                    .unwrap()
3949                    .unwrap();
3950            let (mint_authority_signer, mint_authority) =
3951                config.signer_or_default(arg_matches, "mint_authority", &mut wallet_manager);
3952            let (group_update_authority_signer, group_update_authority) = config.signer_or_default(
3953                arg_matches,
3954                "group_update_authority",
3955                &mut wallet_manager,
3956            );
3957            let mut bulk_signers = vec![mint_authority_signer];
3958            push_signer_with_dedup(group_update_authority_signer, &mut bulk_signers);
3959
3960            command_initialize_member(
3961                config,
3962                member_token_pubkey,
3963                mint_authority,
3964                group_token_pubkey,
3965                group_update_authority,
3966                bulk_signers,
3967            )
3968            .await
3969        }
3970        (CommandName::CreateAccount, arg_matches) => {
3971            let token = pubkey_of_signer(arg_matches, "token", &mut wallet_manager)
3972                .unwrap()
3973                .unwrap();
3974
3975            // No need to add a signer when creating an associated token account
3976            let account = get_signer(arg_matches, "account_keypair", &mut wallet_manager).map(
3977                |(signer, account)| {
3978                    push_signer_with_dedup(signer, &mut bulk_signers);
3979                    account
3980                },
3981            );
3982
3983            let owner = config.pubkey_or_default(arg_matches, "owner", &mut wallet_manager)?;
3984            command_create_account(
3985                config,
3986                token,
3987                owner,
3988                account,
3989                arg_matches.is_present("immutable"),
3990                bulk_signers,
3991            )
3992            .await
3993        }
3994        (CommandName::CreateMultisig, arg_matches) => {
3995            let minimum_signers = arg_matches
3996                .get_one("minimum_signers")
3997                .map(|v: &String| v.parse::<u8>().unwrap())
3998                .unwrap();
3999            let multisig_members =
4000                pubkeys_of_multiple_signers(arg_matches, "multisig_member", &mut wallet_manager)
4001                    .unwrap_or_else(print_error_and_exit)
4002                    .unwrap();
4003            if minimum_signers as usize > multisig_members.len() {
4004                eprintln!(
4005                    "error: MINIMUM_SIGNERS cannot be greater than the number \
4006                          of MULTISIG_MEMBERs passed"
4007                );
4008                exit(1);
4009            }
4010
4011            let (signer, _) = get_signer(arg_matches, "address_keypair", &mut wallet_manager)
4012                .unwrap_or_else(new_throwaway_signer);
4013
4014            command_create_multisig(config, signer, minimum_signers, multisig_members).await
4015        }
4016        (CommandName::Authorize, arg_matches) => {
4017            let address = pubkey_of_signer(arg_matches, "address", &mut wallet_manager)
4018                .unwrap()
4019                .unwrap();
4020            let authority_type = arg_matches.value_of("authority_type").unwrap();
4021            let authority_type = CliAuthorityType::from_str(authority_type)?;
4022
4023            let (authority_signer, authority) =
4024                config.signer_or_default(arg_matches, "authority", &mut wallet_manager);
4025            if config.multisigner_pubkeys.is_empty() {
4026                push_signer_with_dedup(authority_signer, &mut bulk_signers);
4027            }
4028
4029            let new_authority =
4030                pubkey_of_signer(arg_matches, "new_authority", &mut wallet_manager).unwrap();
4031            let force_authorize = arg_matches.is_present("force");
4032            command_authorize(
4033                config,
4034                address,
4035                authority_type,
4036                authority,
4037                new_authority,
4038                force_authorize,
4039                bulk_signers,
4040            )
4041            .await
4042        }
4043        (CommandName::Transfer, arg_matches) => {
4044            let token = pubkey_of_signer(arg_matches, "token", &mut wallet_manager)
4045                .unwrap()
4046                .unwrap();
4047            let amount = *arg_matches.get_one::<Amount>("amount").unwrap();
4048            let recipient = pubkey_of_signer(arg_matches, "recipient", &mut wallet_manager)
4049                .unwrap()
4050                .unwrap();
4051            let sender = pubkey_of_signer(arg_matches, "from", &mut wallet_manager).unwrap();
4052
4053            let (owner_signer, owner) =
4054                config.signer_or_default(arg_matches, "owner", &mut wallet_manager);
4055
4056            let confidential_transfer_args = if arg_matches.is_present("confidential") {
4057                // Deriving ElGamal and AES key from signer. Custom ElGamal and AES keys will be
4058                // supported in the future once upgrading to clap-v3.
4059                //
4060                // NOTE:: Seed bytes are hardcoded to be empty bytes for now. They will be
4061                // updated once custom ElGamal and AES keys are supported.
4062                let sender_elgamal_keypair =
4063                    ElGamalKeypair::new_from_signer(&*owner_signer, b"").unwrap();
4064                let sender_aes_key = AeKey::new_from_signer(&*owner_signer, b"").unwrap();
4065
4066                // Sign-only mode is not yet supported for confidential transfers, so set
4067                // recipient and auditor ElGamal public to `None` by default.
4068                Some(ConfidentialTransferArgs {
4069                    sender_elgamal_keypair,
4070                    sender_aes_key,
4071                    recipient_elgamal_pubkey: None,
4072                    auditor_elgamal_pubkey: None,
4073                })
4074            } else {
4075                None
4076            };
4077
4078            if config.multisigner_pubkeys.is_empty() {
4079                push_signer_with_dedup(owner_signer, &mut bulk_signers);
4080            }
4081
4082            let mint_decimals = arg_matches.get_one::<u8>(MINT_DECIMALS_ARG.name).copied();
4083            let fund_recipient = arg_matches.is_present("fund_recipient");
4084            let allow_unfunded_recipient = arg_matches.is_present("allow_empty_recipient")
4085                || arg_matches.is_present("allow_unfunded_recipient");
4086
4087            let recipient_is_ata_owner = arg_matches.is_present("recipient_is_ata_owner");
4088            let no_recipient_is_ata_owner =
4089                arg_matches.is_present("no_recipient_is_ata_owner") || !recipient_is_ata_owner;
4090            if recipient_is_ata_owner {
4091                println_display(config, "recipient-is-ata-owner is now the default behavior. The option has been deprecated and will be removed in a future release.".to_string());
4092            }
4093            let use_unchecked_instruction = arg_matches.is_present("use_unchecked_instruction");
4094            let expected_fee = arg_matches.get_one::<Amount>("expected_fee").copied();
4095            let memo = value_t!(arg_matches, "memo", String).ok();
4096            let transfer_hook_accounts = arg_matches.values_of("transfer_hook_account").map(|v| {
4097                v.into_iter()
4098                    .map(|s| parse_transfer_hook_account(s).unwrap())
4099                    .collect::<Vec<_>>()
4100            });
4101
4102            command_transfer(
4103                config,
4104                token,
4105                amount,
4106                recipient,
4107                sender,
4108                owner,
4109                allow_unfunded_recipient,
4110                fund_recipient,
4111                mint_decimals,
4112                no_recipient_is_ata_owner,
4113                use_unchecked_instruction,
4114                expected_fee,
4115                memo,
4116                bulk_signers,
4117                arg_matches.is_present("no_wait"),
4118                arg_matches.is_present("allow_non_system_account_recipient"),
4119                transfer_hook_accounts,
4120                confidential_transfer_args.as_ref(),
4121            )
4122            .await
4123        }
4124        (CommandName::Burn, arg_matches) => {
4125            let account = pubkey_of_signer(arg_matches, "account", &mut wallet_manager)
4126                .unwrap()
4127                .unwrap();
4128
4129            let (owner_signer, owner) =
4130                config.signer_or_default(arg_matches, "owner", &mut wallet_manager);
4131            if config.multisigner_pubkeys.is_empty() {
4132                push_signer_with_dedup(owner_signer, &mut bulk_signers);
4133            }
4134
4135            let amount = *arg_matches.get_one::<Amount>("amount").unwrap();
4136            let mint_address =
4137                pubkey_of_signer(arg_matches, MINT_ADDRESS_ARG.name, &mut wallet_manager).unwrap();
4138            let mint_decimals = arg_matches
4139                .get_one(MINT_DECIMALS_ARG.name)
4140                .map(|v: &String| v.parse::<u8>().unwrap());
4141            let use_unchecked_instruction = arg_matches.is_present("use_unchecked_instruction");
4142            let memo = value_t!(arg_matches, "memo", String).ok();
4143            command_burn(
4144                config,
4145                account,
4146                owner,
4147                amount,
4148                mint_address,
4149                mint_decimals,
4150                use_unchecked_instruction,
4151                memo,
4152                bulk_signers,
4153            )
4154            .await
4155        }
4156        (CommandName::Mint, arg_matches) => {
4157            let (mint_authority_signer, mint_authority) =
4158                config.signer_or_default(arg_matches, "mint_authority", &mut wallet_manager);
4159            if config.multisigner_pubkeys.is_empty() {
4160                push_signer_with_dedup(mint_authority_signer, &mut bulk_signers);
4161            }
4162
4163            let token = pubkey_of_signer(arg_matches, "token", &mut wallet_manager)
4164                .unwrap()
4165                .unwrap();
4166            let amount = *arg_matches.get_one::<Amount>("amount").unwrap();
4167            let mint_decimals = arg_matches.get_one::<u8>(MINT_DECIMALS_ARG.name).copied();
4168            let mint_info = config.get_mint_info(&token, mint_decimals).await?;
4169            let recipient = if let Some(address) =
4170                pubkey_of_signer(arg_matches, "recipient", &mut wallet_manager).unwrap()
4171            {
4172                address
4173            } else if let Some(address) =
4174                pubkey_of_signer(arg_matches, "recipient_owner", &mut wallet_manager).unwrap()
4175            {
4176                get_associated_token_address_with_program_id(&address, &token, &config.program_id)
4177            } else {
4178                let owner = config.default_signer()?.pubkey();
4179                config.associated_token_address_for_token_and_program(
4180                    &mint_info.address,
4181                    &owner,
4182                    &mint_info.program_id,
4183                )?
4184            };
4185            config.check_account(&recipient, Some(token)).await?;
4186            let use_unchecked_instruction = arg_matches.is_present("use_unchecked_instruction");
4187            let memo = value_t!(arg_matches, "memo", String).ok();
4188            command_mint(
4189                config,
4190                token,
4191                amount,
4192                recipient,
4193                mint_info,
4194                mint_authority,
4195                use_unchecked_instruction,
4196                memo,
4197                bulk_signers,
4198            )
4199            .await
4200        }
4201        (CommandName::Freeze, arg_matches) => {
4202            let (freeze_authority_signer, freeze_authority) =
4203                config.signer_or_default(arg_matches, "freeze_authority", &mut wallet_manager);
4204            if config.multisigner_pubkeys.is_empty() {
4205                push_signer_with_dedup(freeze_authority_signer, &mut bulk_signers);
4206            }
4207
4208            let account = pubkey_of_signer(arg_matches, "account", &mut wallet_manager)
4209                .unwrap()
4210                .unwrap();
4211            let mint_address =
4212                pubkey_of_signer(arg_matches, MINT_ADDRESS_ARG.name, &mut wallet_manager).unwrap();
4213            command_freeze(
4214                config,
4215                account,
4216                mint_address,
4217                freeze_authority,
4218                bulk_signers,
4219            )
4220            .await
4221        }
4222        (CommandName::Thaw, arg_matches) => {
4223            let (freeze_authority_signer, freeze_authority) =
4224                config.signer_or_default(arg_matches, "freeze_authority", &mut wallet_manager);
4225            if config.multisigner_pubkeys.is_empty() {
4226                push_signer_with_dedup(freeze_authority_signer, &mut bulk_signers);
4227            }
4228
4229            let account = pubkey_of_signer(arg_matches, "account", &mut wallet_manager)
4230                .unwrap()
4231                .unwrap();
4232            let mint_address =
4233                pubkey_of_signer(arg_matches, MINT_ADDRESS_ARG.name, &mut wallet_manager).unwrap();
4234            command_thaw(
4235                config,
4236                account,
4237                mint_address,
4238                freeze_authority,
4239                bulk_signers,
4240            )
4241            .await
4242        }
4243        (CommandName::Wrap, arg_matches) => {
4244            let amount = *arg_matches.get_one::<Amount>("amount").unwrap();
4245            let account = if arg_matches.is_present("create_aux_account") {
4246                let (signer, account) = new_throwaway_signer();
4247                bulk_signers.push(signer);
4248                Some(account)
4249            } else {
4250                // No need to add a signer when creating an associated token account
4251                None
4252            };
4253
4254            let (wallet_signer, wallet_address) =
4255                config.signer_or_default(arg_matches, "wallet_keypair", &mut wallet_manager);
4256            push_signer_with_dedup(wallet_signer, &mut bulk_signers);
4257
4258            command_wrap(
4259                config,
4260                amount,
4261                wallet_address,
4262                account,
4263                arg_matches.is_present("immutable"),
4264                bulk_signers,
4265            )
4266            .await
4267        }
4268        (CommandName::Unwrap, arg_matches) => {
4269            let (wallet_signer, wallet_address) =
4270                config.signer_or_default(arg_matches, "wallet_keypair", &mut wallet_manager);
4271            push_signer_with_dedup(wallet_signer, &mut bulk_signers);
4272
4273            let account = pubkey_of_signer(arg_matches, "account", &mut wallet_manager).unwrap();
4274            command_unwrap(config, wallet_address, account, bulk_signers).await
4275        }
4276        (CommandName::Approve, arg_matches) => {
4277            let (owner_signer, owner_address) =
4278                config.signer_or_default(arg_matches, "owner", &mut wallet_manager);
4279            if config.multisigner_pubkeys.is_empty() {
4280                push_signer_with_dedup(owner_signer, &mut bulk_signers);
4281            }
4282
4283            let account = pubkey_of_signer(arg_matches, "account", &mut wallet_manager)
4284                .unwrap()
4285                .unwrap();
4286            let amount = *arg_matches.get_one::<Amount>("amount").unwrap();
4287            let delegate = pubkey_of_signer(arg_matches, "delegate", &mut wallet_manager)
4288                .unwrap()
4289                .unwrap();
4290            let mint_address =
4291                pubkey_of_signer(arg_matches, MINT_ADDRESS_ARG.name, &mut wallet_manager).unwrap();
4292            let mint_decimals = arg_matches
4293                .get_one(MINT_DECIMALS_ARG.name)
4294                .map(|v: &String| v.parse::<u8>().unwrap());
4295            let use_unchecked_instruction = arg_matches.is_present("use_unchecked_instruction");
4296            command_approve(
4297                config,
4298                account,
4299                owner_address,
4300                amount,
4301                delegate,
4302                mint_address,
4303                mint_decimals,
4304                use_unchecked_instruction,
4305                bulk_signers,
4306            )
4307            .await
4308        }
4309        (CommandName::Revoke, arg_matches) => {
4310            let (owner_signer, owner_address) =
4311                config.signer_or_default(arg_matches, "owner", &mut wallet_manager);
4312            if config.multisigner_pubkeys.is_empty() {
4313                push_signer_with_dedup(owner_signer, &mut bulk_signers);
4314            }
4315
4316            let account = pubkey_of_signer(arg_matches, "account", &mut wallet_manager)
4317                .unwrap()
4318                .unwrap();
4319            let delegate_address =
4320                pubkey_of_signer(arg_matches, DELEGATE_ADDRESS_ARG.name, &mut wallet_manager)
4321                    .unwrap();
4322            command_revoke(
4323                config,
4324                account,
4325                owner_address,
4326                delegate_address,
4327                bulk_signers,
4328            )
4329            .await
4330        }
4331        (CommandName::Close, arg_matches) => {
4332            let (close_authority_signer, close_authority) =
4333                config.signer_or_default(arg_matches, "close_authority", &mut wallet_manager);
4334            if config.multisigner_pubkeys.is_empty() {
4335                push_signer_with_dedup(close_authority_signer, &mut bulk_signers);
4336            }
4337
4338            let address = config
4339                .associated_token_address_or_override(arg_matches, "address", &mut wallet_manager)
4340                .await?;
4341            let recipient =
4342                config.pubkey_or_default(arg_matches, "recipient", &mut wallet_manager)?;
4343            command_close(config, address, close_authority, recipient, bulk_signers).await
4344        }
4345        (CommandName::CloseMint, arg_matches) => {
4346            let token = pubkey_of_signer(arg_matches, "token", &mut wallet_manager)
4347                .unwrap()
4348                .unwrap();
4349            let (close_authority_signer, close_authority) =
4350                config.signer_or_default(arg_matches, "close_authority", &mut wallet_manager);
4351            if config.multisigner_pubkeys.is_empty() {
4352                push_signer_with_dedup(close_authority_signer, &mut bulk_signers);
4353            }
4354            let recipient =
4355                config.pubkey_or_default(arg_matches, "recipient", &mut wallet_manager)?;
4356
4357            command_close_mint(config, token, close_authority, recipient, bulk_signers).await
4358        }
4359        (CommandName::Balance, arg_matches) => {
4360            let address = config
4361                .associated_token_address_or_override(arg_matches, "address", &mut wallet_manager)
4362                .await?;
4363            command_balance(config, address).await
4364        }
4365        (CommandName::Supply, arg_matches) => {
4366            let token = pubkey_of_signer(arg_matches, "token", &mut wallet_manager)
4367                .unwrap()
4368                .unwrap();
4369            command_supply(config, token).await
4370        }
4371        (CommandName::Accounts, arg_matches) => {
4372            let token = pubkey_of_signer(arg_matches, "token", &mut wallet_manager).unwrap();
4373            let owner = config.pubkey_or_default(arg_matches, "owner", &mut wallet_manager)?;
4374            let filter = if arg_matches.is_present("delegated") {
4375                AccountFilter::Delegated
4376            } else if arg_matches.is_present("externally_closeable") {
4377                AccountFilter::ExternallyCloseable
4378            } else {
4379                AccountFilter::All
4380            };
4381
4382            command_accounts(
4383                config,
4384                token,
4385                owner,
4386                filter,
4387                arg_matches.is_present("addresses_only"),
4388            )
4389            .await
4390        }
4391        (CommandName::Address, arg_matches) => {
4392            let token = pubkey_of_signer(arg_matches, "token", &mut wallet_manager).unwrap();
4393            let owner = config.pubkey_or_default(arg_matches, "owner", &mut wallet_manager)?;
4394            command_address(config, token, owner).await
4395        }
4396        (CommandName::AccountInfo, arg_matches) => {
4397            let address = config
4398                .associated_token_address_or_override(arg_matches, "address", &mut wallet_manager)
4399                .await?;
4400            command_display(config, address).await
4401        }
4402        (CommandName::MultisigInfo, arg_matches) => {
4403            let address = pubkey_of_signer(arg_matches, "address", &mut wallet_manager)
4404                .unwrap()
4405                .unwrap();
4406            command_display(config, address).await
4407        }
4408        (CommandName::Display, arg_matches) => {
4409            let address = pubkey_of_signer(arg_matches, "address", &mut wallet_manager)
4410                .unwrap()
4411                .unwrap();
4412            command_display(config, address).await
4413        }
4414        (CommandName::Gc, arg_matches) => {
4415            match config.output_format {
4416                OutputFormat::Json | OutputFormat::JsonCompact => {
4417                    eprintln!(
4418                        "`spl-token gc` does not support the `--output` parameter at this time"
4419                    );
4420                    exit(1);
4421                }
4422                _ => {}
4423            }
4424
4425            let close_empty_associated_accounts =
4426                arg_matches.is_present("close_empty_associated_accounts");
4427
4428            let (owner_signer, owner_address) =
4429                config.signer_or_default(arg_matches, "owner", &mut wallet_manager);
4430            if config.multisigner_pubkeys.is_empty() {
4431                push_signer_with_dedup(owner_signer, &mut bulk_signers);
4432            }
4433
4434            command_gc(
4435                config,
4436                owner_address,
4437                close_empty_associated_accounts,
4438                bulk_signers,
4439            )
4440            .await
4441        }
4442        (CommandName::SyncNative, arg_matches) => {
4443            let native_mint = *native_token_client_from_config(config)?.get_address();
4444            let address = config
4445                .associated_token_address_for_token_or_override(
4446                    arg_matches,
4447                    "address",
4448                    &mut wallet_manager,
4449                    Some(native_mint),
4450                )
4451                .await;
4452            command_sync_native(config, address?).await
4453        }
4454        (CommandName::EnableRequiredTransferMemos, arg_matches) => {
4455            let (owner_signer, owner) =
4456                config.signer_or_default(arg_matches, "owner", &mut wallet_manager);
4457            if config.multisigner_pubkeys.is_empty() {
4458                push_signer_with_dedup(owner_signer, &mut bulk_signers);
4459            }
4460            // Since account is required argument it will always be present
4461            let token_account =
4462                config.pubkey_or_default(arg_matches, "account", &mut wallet_manager)?;
4463            command_required_transfer_memos(config, token_account, owner, bulk_signers, true).await
4464        }
4465        (CommandName::DisableRequiredTransferMemos, arg_matches) => {
4466            let (owner_signer, owner) =
4467                config.signer_or_default(arg_matches, "owner", &mut wallet_manager);
4468            if config.multisigner_pubkeys.is_empty() {
4469                push_signer_with_dedup(owner_signer, &mut bulk_signers);
4470            }
4471            // Since account is required argument it will always be present
4472            let token_account =
4473                config.pubkey_or_default(arg_matches, "account", &mut wallet_manager)?;
4474            command_required_transfer_memos(config, token_account, owner, bulk_signers, false).await
4475        }
4476        (CommandName::EnableCpiGuard, arg_matches) => {
4477            let (owner_signer, owner) =
4478                config.signer_or_default(arg_matches, "owner", &mut wallet_manager);
4479            if config.multisigner_pubkeys.is_empty() {
4480                push_signer_with_dedup(owner_signer, &mut bulk_signers);
4481            }
4482            // Since account is required argument it will always be present
4483            let token_account =
4484                config.pubkey_or_default(arg_matches, "account", &mut wallet_manager)?;
4485            command_cpi_guard(config, token_account, owner, bulk_signers, true).await
4486        }
4487        (CommandName::DisableCpiGuard, arg_matches) => {
4488            let (owner_signer, owner) =
4489                config.signer_or_default(arg_matches, "owner", &mut wallet_manager);
4490            if config.multisigner_pubkeys.is_empty() {
4491                push_signer_with_dedup(owner_signer, &mut bulk_signers);
4492            }
4493            // Since account is required argument it will always be present
4494            let token_account =
4495                config.pubkey_or_default(arg_matches, "account", &mut wallet_manager)?;
4496            command_cpi_guard(config, token_account, owner, bulk_signers, false).await
4497        }
4498        (CommandName::UpdateDefaultAccountState, arg_matches) => {
4499            // Since account is required argument it will always be present
4500            let token = pubkey_of_signer(arg_matches, "token", &mut wallet_manager)
4501                .unwrap()
4502                .unwrap();
4503            let (freeze_authority_signer, freeze_authority) =
4504                config.signer_or_default(arg_matches, "freeze_authority", &mut wallet_manager);
4505            if config.multisigner_pubkeys.is_empty() {
4506                push_signer_with_dedup(freeze_authority_signer, &mut bulk_signers);
4507            }
4508            let new_default_state = arg_matches.value_of("state").unwrap();
4509            let new_default_state = match new_default_state {
4510                "initialized" => AccountState::Initialized,
4511                "frozen" => AccountState::Frozen,
4512                _ => unreachable!(),
4513            };
4514            command_update_default_account_state(
4515                config,
4516                token,
4517                freeze_authority,
4518                new_default_state,
4519                bulk_signers,
4520            )
4521            .await
4522        }
4523        (CommandName::UpdateMetadataAddress, arg_matches) => {
4524            // Since account is required argument it will always be present
4525            let token = pubkey_of_signer(arg_matches, "token", &mut wallet_manager)
4526                .unwrap()
4527                .unwrap();
4528
4529            let (authority_signer, authority) =
4530                config.signer_or_default(arg_matches, "authority", &mut wallet_manager);
4531            if config.multisigner_pubkeys.is_empty() {
4532                push_signer_with_dedup(authority_signer, &mut bulk_signers);
4533            }
4534            let metadata_address = value_t!(arg_matches, "metadata_address", Pubkey).ok();
4535
4536            command_update_pointer_address(
4537                config,
4538                token,
4539                authority,
4540                metadata_address,
4541                bulk_signers,
4542                Pointer::Metadata,
4543            )
4544            .await
4545        }
4546        (CommandName::UpdateGroupAddress, arg_matches) => {
4547            // Since account is required argument it will always be present
4548            let token = pubkey_of_signer(arg_matches, "token", &mut wallet_manager)
4549                .unwrap()
4550                .unwrap();
4551
4552            let (authority_signer, authority) =
4553                config.signer_or_default(arg_matches, "authority", &mut wallet_manager);
4554            if config.multisigner_pubkeys.is_empty() {
4555                push_signer_with_dedup(authority_signer, &mut bulk_signers);
4556            }
4557            let group_address = value_t!(arg_matches, "group_address", Pubkey).ok();
4558
4559            command_update_pointer_address(
4560                config,
4561                token,
4562                authority,
4563                group_address,
4564                bulk_signers,
4565                Pointer::Group,
4566            )
4567            .await
4568        }
4569        (CommandName::UpdateMemberAddress, arg_matches) => {
4570            // Since account is required argument it will always be present
4571            let token = pubkey_of_signer(arg_matches, "token", &mut wallet_manager)
4572                .unwrap()
4573                .unwrap();
4574
4575            let (authority_signer, authority) =
4576                config.signer_or_default(arg_matches, "authority", &mut wallet_manager);
4577            if config.multisigner_pubkeys.is_empty() {
4578                push_signer_with_dedup(authority_signer, &mut bulk_signers);
4579            }
4580            let member_address = value_t!(arg_matches, "member_address", Pubkey).ok();
4581
4582            command_update_pointer_address(
4583                config,
4584                token,
4585                authority,
4586                member_address,
4587                bulk_signers,
4588                Pointer::GroupMember,
4589            )
4590            .await
4591        }
4592        (CommandName::WithdrawWithheldTokens, arg_matches) => {
4593            let (authority_signer, authority) = config.signer_or_default(
4594                arg_matches,
4595                "withdraw_withheld_authority",
4596                &mut wallet_manager,
4597            );
4598            if config.multisigner_pubkeys.is_empty() {
4599                push_signer_with_dedup(authority_signer, &mut bulk_signers);
4600            }
4601            // Since destination is required it will always be present
4602            let destination_token_account =
4603                pubkey_of_signer(arg_matches, "account", &mut wallet_manager)
4604                    .unwrap()
4605                    .unwrap();
4606            let include_mint = arg_matches.is_present("include_mint");
4607            let source_accounts = arg_matches
4608                .values_of("source")
4609                .unwrap_or_default()
4610                .map(|s| Pubkey::from_str(s).unwrap_or_else(print_error_and_exit))
4611                .collect::<Vec<_>>();
4612            command_withdraw_withheld_tokens(
4613                config,
4614                destination_token_account,
4615                source_accounts,
4616                authority,
4617                include_mint,
4618                bulk_signers,
4619            )
4620            .await
4621        }
4622        (CommandName::SetTransferFee, arg_matches) => {
4623            let token_pubkey = pubkey_of_signer(arg_matches, "token", &mut wallet_manager)
4624                .unwrap()
4625                .unwrap();
4626            let transfer_fee_basis_points =
4627                value_t_or_exit!(arg_matches, "transfer_fee_basis_points", u16);
4628            let maximum_fee = *arg_matches.get_one::<Amount>("maximum_fee").unwrap();
4629            let (transfer_fee_authority_signer, transfer_fee_authority_pubkey) = config
4630                .signer_or_default(arg_matches, "transfer_fee_authority", &mut wallet_manager);
4631            let mint_decimals = arg_matches.get_one::<u8>(MINT_DECIMALS_ARG.name).copied();
4632            let bulk_signers = vec![transfer_fee_authority_signer];
4633
4634            command_set_transfer_fee(
4635                config,
4636                token_pubkey,
4637                transfer_fee_authority_pubkey,
4638                transfer_fee_basis_points,
4639                maximum_fee,
4640                mint_decimals,
4641                bulk_signers,
4642            )
4643            .await
4644        }
4645        (CommandName::WithdrawExcessLamports, arg_matches) => {
4646            let (signer, authority) =
4647                config.signer_or_default(arg_matches, "owner", &mut wallet_manager);
4648            if config.multisigner_pubkeys.is_empty() {
4649                push_signer_with_dedup(signer, &mut bulk_signers);
4650            }
4651
4652            let source = config.pubkey_or_default(arg_matches, "from", &mut wallet_manager)?;
4653            let destination =
4654                config.pubkey_or_default(arg_matches, "recipient", &mut wallet_manager)?;
4655
4656            command_withdraw_excess_lamports(config, source, destination, authority, bulk_signers)
4657                .await
4658        }
4659        (CommandName::UpdateConfidentialTransferSettings, arg_matches) => {
4660            let token_pubkey = pubkey_of_signer(arg_matches, "token", &mut wallet_manager)
4661                .unwrap()
4662                .unwrap();
4663
4664            let auto_approve = arg_matches.value_of("approve_policy").map(|b| b == "auto");
4665
4666            let auditor_encryption_pubkey = if arg_matches.is_present("auditor_pubkey") {
4667                Some(elgamal_pubkey_or_none(arg_matches, "auditor_pubkey")?)
4668            } else {
4669                None
4670            };
4671
4672            let (authority_signer, authority_pubkey) = config.signer_or_default(
4673                arg_matches,
4674                "confidential_transfer_authority",
4675                &mut wallet_manager,
4676            );
4677            let bulk_signers = vec![authority_signer];
4678
4679            command_update_confidential_transfer_settings(
4680                config,
4681                token_pubkey,
4682                authority_pubkey,
4683                auto_approve,
4684                auditor_encryption_pubkey,
4685                bulk_signers,
4686            )
4687            .await
4688        }
4689        (CommandName::ConfigureConfidentialTransferAccount, arg_matches) => {
4690            let token = pubkey_of_signer(arg_matches, "token", &mut wallet_manager).unwrap();
4691
4692            let (owner_signer, owner) =
4693                config.signer_or_default(arg_matches, "owner", &mut wallet_manager);
4694
4695            let account = pubkey_of_signer(arg_matches, "address", &mut wallet_manager).unwrap();
4696
4697            // Deriving ElGamal and AES key from signer. Custom ElGamal and AES keys will be
4698            // supported in the future once upgrading to clap-v3.
4699            //
4700            // NOTE:: Seed bytes are hardcoded to be empty bytes for now. They will be
4701            // updated once custom ElGamal and AES keys are supported.
4702            let elgamal_keypair = ElGamalKeypair::new_from_signer(&*owner_signer, b"").unwrap();
4703            let aes_key = AeKey::new_from_signer(&*owner_signer, b"").unwrap();
4704
4705            if config.multisigner_pubkeys.is_empty() {
4706                push_signer_with_dedup(owner_signer, &mut bulk_signers);
4707            }
4708
4709            let maximum_credit_counter =
4710                if arg_matches.is_present("maximum_pending_balance_credit_counter") {
4711                    let maximum_credit_counter = value_t_or_exit!(
4712                        arg_matches.value_of("maximum_pending_balance_credit_counter"),
4713                        u64
4714                    );
4715                    Some(maximum_credit_counter)
4716                } else {
4717                    None
4718                };
4719
4720            command_configure_confidential_transfer_account(
4721                config,
4722                token,
4723                owner,
4724                account,
4725                maximum_credit_counter,
4726                &elgamal_keypair,
4727                &aes_key,
4728                bulk_signers,
4729            )
4730            .await
4731        }
4732        (c @ CommandName::EnableConfidentialCredits, arg_matches)
4733        | (c @ CommandName::DisableConfidentialCredits, arg_matches)
4734        | (c @ CommandName::EnableNonConfidentialCredits, arg_matches)
4735        | (c @ CommandName::DisableNonConfidentialCredits, arg_matches) => {
4736            let token = pubkey_of_signer(arg_matches, "token", &mut wallet_manager).unwrap();
4737
4738            let (owner_signer, owner) =
4739                config.signer_or_default(arg_matches, "owner", &mut wallet_manager);
4740
4741            let account = pubkey_of_signer(arg_matches, "address", &mut wallet_manager).unwrap();
4742
4743            if config.multisigner_pubkeys.is_empty() {
4744                push_signer_with_dedup(owner_signer, &mut bulk_signers);
4745            }
4746
4747            let (allow_confidential_credits, allow_non_confidential_credits) = match c {
4748                CommandName::EnableConfidentialCredits => (Some(true), None),
4749                CommandName::DisableConfidentialCredits => (Some(false), None),
4750                CommandName::EnableNonConfidentialCredits => (None, Some(true)),
4751                CommandName::DisableNonConfidentialCredits => (None, Some(false)),
4752                _ => (None, None),
4753            };
4754
4755            command_enable_disable_confidential_transfers(
4756                config,
4757                token,
4758                owner,
4759                account,
4760                bulk_signers,
4761                allow_confidential_credits,
4762                allow_non_confidential_credits,
4763            )
4764            .await
4765        }
4766        (c @ CommandName::DepositConfidentialTokens, arg_matches)
4767        | (c @ CommandName::WithdrawConfidentialTokens, arg_matches) => {
4768            let token = pubkey_of_signer(arg_matches, "token", &mut wallet_manager)
4769                .unwrap()
4770                .unwrap();
4771            let amount = *arg_matches.get_one::<Amount>("amount").unwrap();
4772            let account = pubkey_of_signer(arg_matches, "address", &mut wallet_manager).unwrap();
4773
4774            let (owner_signer, owner) =
4775                config.signer_or_default(arg_matches, "owner", &mut wallet_manager);
4776            let mint_decimals = arg_matches.get_one::<u8>(MINT_DECIMALS_ARG.name).copied();
4777
4778            let (instruction_type, elgamal_keypair, aes_key) = match c {
4779                CommandName::DepositConfidentialTokens => {
4780                    (ConfidentialInstructionType::Deposit, None, None)
4781                }
4782                CommandName::WithdrawConfidentialTokens => {
4783                    // Deriving ElGamal and AES key from signer. Custom ElGamal and AES keys will be
4784                    // supported in the future once upgrading to clap-v3.
4785                    //
4786                    // NOTE:: Seed bytes are hardcoded to be empty bytes for now. They will be
4787                    // updated once custom ElGamal and AES keys are supported.
4788                    let elgamal_keypair =
4789                        ElGamalKeypair::new_from_signer(&*owner_signer, b"").unwrap();
4790                    let aes_key = AeKey::new_from_signer(&*owner_signer, b"").unwrap();
4791
4792                    (
4793                        ConfidentialInstructionType::Withdraw,
4794                        Some(elgamal_keypair),
4795                        Some(aes_key),
4796                    )
4797                }
4798                _ => panic!("Instruction not supported"),
4799            };
4800
4801            if config.multisigner_pubkeys.is_empty() {
4802                push_signer_with_dedup(owner_signer, &mut bulk_signers);
4803            }
4804
4805            command_deposit_withdraw_confidential_tokens(
4806                config,
4807                token,
4808                owner,
4809                account,
4810                bulk_signers,
4811                amount,
4812                mint_decimals,
4813                instruction_type,
4814                elgamal_keypair.as_ref(),
4815                aes_key.as_ref(),
4816            )
4817            .await
4818        }
4819        (CommandName::ApplyPendingBalance, arg_matches) => {
4820            let token = pubkey_of_signer(arg_matches, "token", &mut wallet_manager).unwrap();
4821
4822            let (owner_signer, owner) =
4823                config.signer_or_default(arg_matches, "owner", &mut wallet_manager);
4824
4825            let account = pubkey_of_signer(arg_matches, "address", &mut wallet_manager).unwrap();
4826
4827            // Deriving ElGamal and AES key from signer. Custom ElGamal and AES keys will be
4828            // supported in the future once upgrading to clap-v3.
4829            //
4830            // NOTE:: Seed bytes are hardcoded to be empty bytes for now. They will be
4831            // updated once custom ElGamal and AES keys are supported.
4832            let elgamal_keypair = ElGamalKeypair::new_from_signer(&*owner_signer, b"").unwrap();
4833            let aes_key = AeKey::new_from_signer(&*owner_signer, b"").unwrap();
4834
4835            if config.multisigner_pubkeys.is_empty() {
4836                push_signer_with_dedup(owner_signer, &mut bulk_signers);
4837            }
4838
4839            command_apply_pending_balance(
4840                config,
4841                token,
4842                owner,
4843                account,
4844                bulk_signers,
4845                &elgamal_keypair,
4846                &aes_key,
4847            )
4848            .await
4849        }
4850        (CommandName::UpdateUiAmountMultiplier, arg_matches) => {
4851            let token_pubkey = pubkey_of_signer(arg_matches, "token", &mut wallet_manager)
4852                .unwrap()
4853                .unwrap();
4854            let new_multiplier = value_t_or_exit!(arg_matches, "multiplier", f64);
4855            let new_multiplier_effective_timestamp =
4856                if let Some(timestamp) = arg_matches.value_of("timestamp") {
4857                    timestamp.parse::<i64>().unwrap()
4858                } else {
4859                    SystemTime::now()
4860                        .duration_since(UNIX_EPOCH)
4861                        .unwrap()
4862                        .as_secs() as i64
4863                };
4864            let (ui_multiplier_authority_signer, ui_multiplier_authority_pubkey) = config
4865                .signer_or_default(arg_matches, "ui_multiplier_authority", &mut wallet_manager);
4866            let bulk_signers = vec![ui_multiplier_authority_signer];
4867
4868            command_update_multiplier(
4869                config,
4870                token_pubkey,
4871                ui_multiplier_authority_pubkey,
4872                new_multiplier,
4873                new_multiplier_effective_timestamp,
4874                bulk_signers,
4875            )
4876            .await
4877        }
4878        (c @ CommandName::Pause, arg_matches) | (c @ CommandName::Resume, arg_matches) => {
4879            let token_pubkey = pubkey_of_signer(arg_matches, "token", &mut wallet_manager)
4880                .unwrap()
4881                .unwrap();
4882            let (pause_authority_signer, pause_authority_pubkey) =
4883                config.signer_or_default(arg_matches, "pause_authority", &mut wallet_manager);
4884            let bulk_signers = vec![pause_authority_signer];
4885
4886            let allow_mint_burn_transfer = match c {
4887                CommandName::Pause => false,
4888                CommandName::Resume => true,
4889                _ => panic!("Instruction not supported"),
4890            };
4891
4892            command_pause_resume(
4893                config,
4894                token_pubkey,
4895                pause_authority_pubkey,
4896                bulk_signers,
4897                allow_mint_burn_transfer,
4898            )
4899            .await
4900        }
4901    }
4902}
4903
4904fn format_output<T>(command_output: T, command_name: &CommandName, config: &Config) -> String
4905where
4906    T: Serialize + Display + QuietDisplay + VerboseDisplay,
4907{
4908    config.output_format.formatted_string(&CommandOutput {
4909        command_name: command_name.to_string(),
4910        command_output,
4911    })
4912}
4913enum TransactionReturnData {
4914    CliSignature(CliSignature),
4915    CliSignOnlyData(CliSignOnlyData),
4916}
4917
4918async fn finish_tx(
4919    config: &Config<'_>,
4920    rpc_response: &RpcClientResponse,
4921    no_wait: bool,
4922) -> Result<TransactionReturnData, Error> {
4923    match rpc_response {
4924        RpcClientResponse::Transaction(transaction) => {
4925            Ok(TransactionReturnData::CliSignOnlyData(return_signers_data(
4926                transaction,
4927                &ReturnSignersConfig {
4928                    dump_transaction_message: config.dump_transaction_message,
4929                },
4930            )))
4931        }
4932        RpcClientResponse::Signature(signature) if no_wait => {
4933            Ok(TransactionReturnData::CliSignature(CliSignature {
4934                signature: signature.to_string(),
4935            }))
4936        }
4937        RpcClientResponse::Signature(signature) => {
4938            let blockhash = config.program_client.get_latest_blockhash().await?;
4939            config
4940                .rpc_client
4941                .confirm_transaction_with_spinner(
4942                    signature,
4943                    &blockhash,
4944                    config.rpc_client.commitment(),
4945                )
4946                .await?;
4947
4948            Ok(TransactionReturnData::CliSignature(CliSignature {
4949                signature: signature.to_string(),
4950            }))
4951        }
4952        RpcClientResponse::Simulation(_) => {
4953            // Implement this once the CLI supports dry-running / simulation
4954            unreachable!()
4955        }
4956    }
4957}