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