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