Skip to main content

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