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