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