1use {
2 crate::cli::{CliCommand, CliCommandInfo, CliConfig, CliError, ProcessResult},
3 clap::{App, AppSettings, Arg, ArgMatches, SubCommand},
4 solana_address_lookup_table_interface::{
5 self as address_lookup_table,
6 instruction::{
7 close_lookup_table, create_lookup_table, deactivate_lookup_table, extend_lookup_table,
8 freeze_lookup_table,
9 },
10 state::AddressLookupTable,
11 },
12 solana_clap_utils::{self, input_parsers::*, input_validators::*, keypair::*},
13 solana_cli_output::{CliAddressLookupTable, CliAddressLookupTableCreated, CliSignature},
14 solana_clock::Clock,
15 solana_commitment_config::CommitmentConfig,
16 solana_message::Message,
17 solana_pubkey::Pubkey,
18 solana_remote_wallet::remote_wallet::RemoteWalletManager,
19 solana_rpc_client::nonblocking::rpc_client::RpcClient,
20 solana_rpc_client_api::config::RpcSendTransactionConfig,
21 solana_sdk_ids::sysvar,
22 solana_signer::Signer,
23 solana_slot_hashes::SlotHashes,
24 solana_transaction::Transaction,
25 std::{rc::Rc, sync::Arc},
26};
27
28#[derive(Debug, PartialEq, Eq)]
29pub enum AddressLookupTableCliCommand {
30 CreateLookupTable {
31 authority_pubkey: Pubkey,
32 payer_signer_index: SignerIndex,
33 },
34 FreezeLookupTable {
35 lookup_table_pubkey: Pubkey,
36 authority_signer_index: SignerIndex,
37 bypass_warning: bool,
38 },
39 ExtendLookupTable {
40 lookup_table_pubkey: Pubkey,
41 authority_signer_index: SignerIndex,
42 payer_signer_index: SignerIndex,
43 new_addresses: Vec<Pubkey>,
44 },
45 DeactivateLookupTable {
46 lookup_table_pubkey: Pubkey,
47 authority_signer_index: SignerIndex,
48 bypass_warning: bool,
49 },
50 CloseLookupTable {
51 lookup_table_pubkey: Pubkey,
52 authority_signer_index: SignerIndex,
53 recipient_pubkey: Pubkey,
54 },
55 ShowLookupTable {
56 lookup_table_pubkey: Pubkey,
57 },
58}
59
60pub trait AddressLookupTableSubCommands {
61 fn address_lookup_table_subcommands(self) -> Self;
62}
63
64impl AddressLookupTableSubCommands for App<'_, '_> {
65 fn address_lookup_table_subcommands(self) -> Self {
66 self.subcommand(
67 SubCommand::with_name("address-lookup-table")
68 .about("Address lookup table management")
69 .setting(AppSettings::SubcommandRequiredElseHelp)
70 .subcommand(
71 SubCommand::with_name("create")
72 .about("Create a lookup table")
73 .arg(
74 Arg::with_name("authority")
75 .long("authority")
76 .alias("authority-signer")
77 .value_name("AUTHORITY_PUBKEY")
78 .takes_value(true)
79 .validator(is_pubkey_or_keypair)
80 .help(
81 "Lookup table authority address [default: the default \
82 configured keypair].",
83 ),
84 )
85 .arg(
86 Arg::with_name("payer")
87 .long("payer")
88 .value_name("PAYER_SIGNER")
89 .takes_value(true)
90 .validator(is_valid_signer)
91 .help(
92 "Account that will pay rent fees for the created lookup table \
93 [default: the default configured keypair]",
94 ),
95 ),
96 )
97 .subcommand(
98 SubCommand::with_name("freeze")
99 .about("Permanently freezes a lookup table")
100 .arg(
101 Arg::with_name("lookup_table_address")
102 .index(1)
103 .value_name("LOOKUP_TABLE_ADDRESS")
104 .takes_value(true)
105 .required(true)
106 .validator(is_pubkey)
107 .help("Address of the lookup table"),
108 )
109 .arg(
110 Arg::with_name("authority")
111 .long("authority")
112 .value_name("AUTHORITY_SIGNER")
113 .takes_value(true)
114 .validator(is_valid_signer)
115 .help(
116 "Lookup table authority [default: the default configured \
117 keypair]",
118 ),
119 )
120 .arg(
121 Arg::with_name("bypass_warning")
122 .long("bypass-warning")
123 .takes_value(false)
124 .help("Bypass the permanent lookup table freeze warning"),
125 ),
126 )
127 .subcommand(
128 SubCommand::with_name("extend")
129 .about("Append more addresses to a lookup table")
130 .arg(
131 Arg::with_name("lookup_table_address")
132 .index(1)
133 .value_name("LOOKUP_TABLE_ADDRESS")
134 .takes_value(true)
135 .required(true)
136 .validator(is_pubkey)
137 .help("Address of the lookup table"),
138 )
139 .arg(
140 Arg::with_name("authority")
141 .long("authority")
142 .value_name("AUTHORITY_SIGNER")
143 .takes_value(true)
144 .validator(is_valid_signer)
145 .help(
146 "Lookup table authority [default: the default configured \
147 keypair]",
148 ),
149 )
150 .arg(
151 Arg::with_name("payer")
152 .long("payer")
153 .value_name("PAYER_SIGNER")
154 .takes_value(true)
155 .validator(is_valid_signer)
156 .help(
157 "Account that will pay rent fees for the extended lookup \
158 table [default: the default configured keypair]",
159 ),
160 )
161 .arg(
162 Arg::with_name("addresses")
163 .long("addresses")
164 .value_name("ADDRESS_1,ADDRESS_2")
165 .takes_value(true)
166 .use_delimiter(true)
167 .required(true)
168 .validator(is_pubkey)
169 .help("Comma separated list of addresses to append"),
170 ),
171 )
172 .subcommand(
173 SubCommand::with_name("deactivate")
174 .about("Permanently deactivates a lookup table")
175 .arg(
176 Arg::with_name("lookup_table_address")
177 .index(1)
178 .value_name("LOOKUP_TABLE_ADDRESS")
179 .takes_value(true)
180 .required(true)
181 .help("Address of the lookup table"),
182 )
183 .arg(
184 Arg::with_name("authority")
185 .long("authority")
186 .value_name("AUTHORITY_SIGNER")
187 .takes_value(true)
188 .validator(is_valid_signer)
189 .help(
190 "Lookup table authority [default: the default configured \
191 keypair]",
192 ),
193 )
194 .arg(
195 Arg::with_name("bypass_warning")
196 .long("bypass-warning")
197 .takes_value(false)
198 .help("Bypass the permanent lookup table deactivation warning"),
199 ),
200 )
201 .subcommand(
202 SubCommand::with_name("close")
203 .about("Permanently closes a lookup table")
204 .arg(
205 Arg::with_name("lookup_table_address")
206 .index(1)
207 .value_name("LOOKUP_TABLE_ADDRESS")
208 .takes_value(true)
209 .required(true)
210 .help("Address of the lookup table"),
211 )
212 .arg(
213 Arg::with_name("recipient")
214 .long("recipient")
215 .value_name("RECIPIENT_ADDRESS")
216 .takes_value(true)
217 .validator(is_pubkey)
218 .help(
219 "Address of the recipient account to deposit the closed \
220 account's lamports [default: the default configured keypair]",
221 ),
222 )
223 .arg(
224 Arg::with_name("authority")
225 .long("authority")
226 .value_name("AUTHORITY_SIGNER")
227 .takes_value(true)
228 .validator(is_valid_signer)
229 .help(
230 "Lookup table authority [default: the default configured \
231 keypair]",
232 ),
233 ),
234 )
235 .subcommand(
236 SubCommand::with_name("get")
237 .about("Display information about a lookup table")
238 .arg(
239 Arg::with_name("lookup_table_address")
240 .index(1)
241 .value_name("LOOKUP_TABLE_ADDRESS")
242 .takes_value(true)
243 .required(true)
244 .help("Address of the lookup table to show"),
245 ),
246 ),
247 )
248 }
249}
250
251pub fn parse_address_lookup_table_subcommand(
252 matches: &ArgMatches<'_>,
253 default_signer: &DefaultSigner,
254 wallet_manager: &mut Option<Rc<RemoteWalletManager>>,
255) -> Result<CliCommandInfo, CliError> {
256 let (subcommand, sub_matches) = matches.subcommand();
257
258 let response = match (subcommand, sub_matches) {
259 ("create", Some(matches)) => {
260 let mut bulk_signers = vec![Some(
261 default_signer.signer_from_path(matches, wallet_manager)?,
262 )];
263
264 let authority_pubkey = if let Some(authority_pubkey) = pubkey_of(matches, "authority") {
265 authority_pubkey
266 } else {
267 default_signer
268 .signer_from_path(matches, wallet_manager)?
269 .pubkey()
270 };
271
272 let payer_pubkey = if let Ok((payer_signer, Some(payer_pubkey))) =
273 signer_of(matches, "payer", wallet_manager)
274 {
275 bulk_signers.push(payer_signer);
276 Some(payer_pubkey)
277 } else {
278 Some(
279 default_signer
280 .signer_from_path(matches, wallet_manager)?
281 .pubkey(),
282 )
283 };
284
285 let signer_info =
286 default_signer.generate_unique_signers(bulk_signers, matches, wallet_manager)?;
287
288 CliCommandInfo {
289 command: CliCommand::AddressLookupTable(
290 AddressLookupTableCliCommand::CreateLookupTable {
291 authority_pubkey,
292 payer_signer_index: signer_info.index_of(payer_pubkey).unwrap(),
293 },
294 ),
295 signers: signer_info.signers,
296 }
297 }
298 ("freeze", Some(matches)) => {
299 let lookup_table_pubkey = pubkey_of(matches, "lookup_table_address").unwrap();
300
301 let mut bulk_signers = vec![Some(
302 default_signer.signer_from_path(matches, wallet_manager)?,
303 )];
304
305 let authority_pubkey = if let Ok((authority_signer, Some(authority_pubkey))) =
306 signer_of(matches, "authority", wallet_manager)
307 {
308 bulk_signers.push(authority_signer);
309 Some(authority_pubkey)
310 } else {
311 Some(
312 default_signer
313 .signer_from_path(matches, wallet_manager)?
314 .pubkey(),
315 )
316 };
317
318 let signer_info =
319 default_signer.generate_unique_signers(bulk_signers, matches, wallet_manager)?;
320
321 CliCommandInfo {
322 command: CliCommand::AddressLookupTable(
323 AddressLookupTableCliCommand::FreezeLookupTable {
324 lookup_table_pubkey,
325 authority_signer_index: signer_info.index_of(authority_pubkey).unwrap(),
326 bypass_warning: matches.is_present("bypass_warning"),
327 },
328 ),
329 signers: signer_info.signers,
330 }
331 }
332 ("extend", Some(matches)) => {
333 let lookup_table_pubkey = pubkey_of(matches, "lookup_table_address").unwrap();
334
335 let mut bulk_signers = vec![Some(
336 default_signer.signer_from_path(matches, wallet_manager)?,
337 )];
338
339 let authority_pubkey = if let Ok((authority_signer, Some(authority_pubkey))) =
340 signer_of(matches, "authority", wallet_manager)
341 {
342 bulk_signers.push(authority_signer);
343 Some(authority_pubkey)
344 } else {
345 Some(
346 default_signer
347 .signer_from_path(matches, wallet_manager)?
348 .pubkey(),
349 )
350 };
351
352 let payer_pubkey = if let Ok((payer_signer, Some(payer_pubkey))) =
353 signer_of(matches, "payer", wallet_manager)
354 {
355 bulk_signers.push(payer_signer);
356 Some(payer_pubkey)
357 } else {
358 Some(
359 default_signer
360 .signer_from_path(matches, wallet_manager)?
361 .pubkey(),
362 )
363 };
364
365 let new_addresses: Vec<Pubkey> = values_of(matches, "addresses").unwrap();
366
367 let signer_info =
368 default_signer.generate_unique_signers(bulk_signers, matches, wallet_manager)?;
369
370 CliCommandInfo {
371 command: CliCommand::AddressLookupTable(
372 AddressLookupTableCliCommand::ExtendLookupTable {
373 lookup_table_pubkey,
374 authority_signer_index: signer_info.index_of(authority_pubkey).unwrap(),
375 payer_signer_index: signer_info.index_of(payer_pubkey).unwrap(),
376 new_addresses,
377 },
378 ),
379 signers: signer_info.signers,
380 }
381 }
382 ("deactivate", Some(matches)) => {
383 let lookup_table_pubkey = pubkey_of(matches, "lookup_table_address").unwrap();
384
385 let mut bulk_signers = vec![Some(
386 default_signer.signer_from_path(matches, wallet_manager)?,
387 )];
388
389 let authority_pubkey = if let Ok((authority_signer, Some(authority_pubkey))) =
390 signer_of(matches, "authority", wallet_manager)
391 {
392 bulk_signers.push(authority_signer);
393 Some(authority_pubkey)
394 } else {
395 Some(
396 default_signer
397 .signer_from_path(matches, wallet_manager)?
398 .pubkey(),
399 )
400 };
401
402 let signer_info =
403 default_signer.generate_unique_signers(bulk_signers, matches, wallet_manager)?;
404
405 CliCommandInfo {
406 command: CliCommand::AddressLookupTable(
407 AddressLookupTableCliCommand::DeactivateLookupTable {
408 lookup_table_pubkey,
409 authority_signer_index: signer_info.index_of(authority_pubkey).unwrap(),
410 bypass_warning: matches.is_present("bypass_warning"),
411 },
412 ),
413 signers: signer_info.signers,
414 }
415 }
416 ("close", Some(matches)) => {
417 let lookup_table_pubkey = pubkey_of(matches, "lookup_table_address").unwrap();
418
419 let mut bulk_signers = vec![Some(
420 default_signer.signer_from_path(matches, wallet_manager)?,
421 )];
422
423 let authority_pubkey = if let Ok((authority_signer, Some(authority_pubkey))) =
424 signer_of(matches, "authority", wallet_manager)
425 {
426 bulk_signers.push(authority_signer);
427 Some(authority_pubkey)
428 } else {
429 Some(
430 default_signer
431 .signer_from_path(matches, wallet_manager)?
432 .pubkey(),
433 )
434 };
435
436 let recipient_pubkey = if let Some(recipient_pubkey) = pubkey_of(matches, "recipient") {
437 recipient_pubkey
438 } else {
439 default_signer
440 .signer_from_path(matches, wallet_manager)?
441 .pubkey()
442 };
443
444 let signer_info =
445 default_signer.generate_unique_signers(bulk_signers, matches, wallet_manager)?;
446
447 CliCommandInfo {
448 command: CliCommand::AddressLookupTable(
449 AddressLookupTableCliCommand::CloseLookupTable {
450 lookup_table_pubkey,
451 authority_signer_index: signer_info.index_of(authority_pubkey).unwrap(),
452 recipient_pubkey,
453 },
454 ),
455 signers: signer_info.signers,
456 }
457 }
458 ("get", Some(matches)) => {
459 let lookup_table_pubkey = pubkey_of(matches, "lookup_table_address").unwrap();
460
461 CliCommandInfo::without_signers(CliCommand::AddressLookupTable(
462 AddressLookupTableCliCommand::ShowLookupTable {
463 lookup_table_pubkey,
464 },
465 ))
466 }
467 _ => unreachable!(),
468 };
469 Ok(response)
470}
471
472pub async fn process_address_lookup_table_subcommand(
473 rpc_client: Arc<RpcClient>,
474 config: &CliConfig<'_>,
475 subcommand: &AddressLookupTableCliCommand,
476) -> ProcessResult {
477 match subcommand {
478 AddressLookupTableCliCommand::CreateLookupTable {
479 authority_pubkey,
480 payer_signer_index,
481 } => {
482 process_create_lookup_table(&rpc_client, config, *authority_pubkey, *payer_signer_index)
483 .await
484 }
485 AddressLookupTableCliCommand::FreezeLookupTable {
486 lookup_table_pubkey,
487 authority_signer_index,
488 bypass_warning,
489 } => {
490 process_freeze_lookup_table(
491 &rpc_client,
492 config,
493 *lookup_table_pubkey,
494 *authority_signer_index,
495 *bypass_warning,
496 )
497 .await
498 }
499 AddressLookupTableCliCommand::ExtendLookupTable {
500 lookup_table_pubkey,
501 authority_signer_index,
502 payer_signer_index,
503 new_addresses,
504 } => {
505 process_extend_lookup_table(
506 &rpc_client,
507 config,
508 *lookup_table_pubkey,
509 *authority_signer_index,
510 *payer_signer_index,
511 new_addresses.to_vec(),
512 )
513 .await
514 }
515 AddressLookupTableCliCommand::DeactivateLookupTable {
516 lookup_table_pubkey,
517 authority_signer_index,
518 bypass_warning,
519 } => {
520 process_deactivate_lookup_table(
521 &rpc_client,
522 config,
523 *lookup_table_pubkey,
524 *authority_signer_index,
525 *bypass_warning,
526 )
527 .await
528 }
529 AddressLookupTableCliCommand::CloseLookupTable {
530 lookup_table_pubkey,
531 authority_signer_index,
532 recipient_pubkey,
533 } => {
534 process_close_lookup_table(
535 &rpc_client,
536 config,
537 *lookup_table_pubkey,
538 *authority_signer_index,
539 *recipient_pubkey,
540 )
541 .await
542 }
543 AddressLookupTableCliCommand::ShowLookupTable {
544 lookup_table_pubkey,
545 } => process_show_lookup_table(&rpc_client, config, *lookup_table_pubkey).await,
546 }
547}
548
549async fn process_create_lookup_table(
550 rpc_client: &RpcClient,
551 config: &CliConfig<'_>,
552 authority_address: Pubkey,
553 payer_signer_index: usize,
554) -> ProcessResult {
555 let payer_signer = config.signers[payer_signer_index];
556
557 let get_clock_result = rpc_client
558 .get_account_with_commitment(&sysvar::clock::id(), CommitmentConfig::finalized())
559 .await?;
560 let clock_account = get_clock_result.value.expect("Clock account doesn't exist");
561 let clock: Clock = wincode::deserialize(&clock_account.data)
562 .map_err(|_| CliError::RpcRequestError("Failed to deserialize clock sysvar".to_string()))?;
563
564 let get_slot_hashes_result = rpc_client
565 .get_account_with_commitment(&sysvar::slot_hashes::id(), config.commitment)
566 .await?;
567 let slot_hashes_account = get_slot_hashes_result.value.ok_or_else(|| {
568 CliError::RpcRequestError("Slot hashes sysvar account doesn't exist".to_string())
569 })?;
570 let slot_hashes: SlotHashes =
571 wincode::deserialize(&slot_hashes_account.data).map_err(|_| {
572 CliError::RpcRequestError("Failed to deserialize slot hashes sysvar".to_string())
573 })?;
574
575 let recent_slot = slot_hashes
578 .iter()
579 .find(|slot_hash| slot_hash.slot <= clock.slot)
580 .ok_or_else(|| {
581 CliError::RpcRequestError(
582 "No recent finalized slot found in slot hashes sysvar".to_string(),
583 )
584 })?
585 .slot;
586
587 let payer_address = payer_signer.pubkey();
588 let (create_lookup_table_ix, lookup_table_address) =
589 create_lookup_table(authority_address, payer_address, recent_slot);
590
591 let blockhash = rpc_client.get_latest_blockhash().await?;
592 let mut tx = Transaction::new_unsigned(Message::new(
593 &[create_lookup_table_ix],
594 Some(&config.signers[0].pubkey()),
595 ));
596
597 let keypairs: Vec<&dyn Signer> = vec![config.signers[0], payer_signer];
598 tx.try_sign(&keypairs, blockhash)?;
599 let result = rpc_client
600 .send_and_confirm_transaction_with_spinner_and_config(
601 &tx,
602 config.commitment,
603 RpcSendTransactionConfig {
604 skip_preflight: false,
605 preflight_commitment: Some(config.commitment.commitment),
606 ..RpcSendTransactionConfig::default()
607 },
608 )
609 .await;
610 match result {
611 Err(err) => Err(format!("Create failed: {err}").into()),
612 Ok(signature) => Ok(config
613 .output_format
614 .formatted_string(&CliAddressLookupTableCreated {
615 lookup_table_address: lookup_table_address.to_string(),
616 signature: signature.to_string(),
617 })),
618 }
619}
620
621pub const FREEZE_LOOKUP_TABLE_WARNING: &str =
622 "WARNING! Once a lookup table is frozen, it can never be modified or unfrozen again. To \
623 proceed with freezing, rerun the `freeze` command with the `--bypass-warning` flag";
624
625async fn process_freeze_lookup_table(
626 rpc_client: &RpcClient,
627 config: &CliConfig<'_>,
628 lookup_table_pubkey: Pubkey,
629 authority_signer_index: usize,
630 bypass_warning: bool,
631) -> ProcessResult {
632 let authority_signer = config.signers[authority_signer_index];
633
634 let get_lookup_table_result = rpc_client
635 .get_account_with_commitment(&lookup_table_pubkey, config.commitment)
636 .await?;
637 let lookup_table_account = get_lookup_table_result.value.ok_or_else(|| {
638 format!("Lookup table account {lookup_table_pubkey} not found, was it already closed?")
639 })?;
640 if !address_lookup_table::program::check_id(&lookup_table_account.owner) {
641 return Err(format!(
642 "Lookup table account {lookup_table_pubkey} is not owned by the Address Lookup Table \
643 program",
644 )
645 .into());
646 }
647
648 if !bypass_warning {
649 return Err(String::from(FREEZE_LOOKUP_TABLE_WARNING).into());
650 }
651
652 let authority_address = authority_signer.pubkey();
653 let freeze_lookup_table_ix = freeze_lookup_table(lookup_table_pubkey, authority_address);
654
655 let blockhash = rpc_client.get_latest_blockhash().await?;
656 let mut tx = Transaction::new_unsigned(Message::new(
657 &[freeze_lookup_table_ix],
658 Some(&config.signers[0].pubkey()),
659 ));
660
661 tx.try_sign(&[config.signers[0], authority_signer], blockhash)?;
662 let result = rpc_client
663 .send_and_confirm_transaction_with_spinner_and_config(
664 &tx,
665 config.commitment,
666 RpcSendTransactionConfig {
667 skip_preflight: false,
668 preflight_commitment: Some(config.commitment.commitment),
669 ..RpcSendTransactionConfig::default()
670 },
671 )
672 .await;
673 match result {
674 Err(err) => Err(format!("Freeze failed: {err}").into()),
675 Ok(signature) => Ok(config.output_format.formatted_string(&CliSignature {
676 signature: signature.to_string(),
677 })),
678 }
679}
680
681async fn process_extend_lookup_table(
682 rpc_client: &RpcClient,
683 config: &CliConfig<'_>,
684 lookup_table_pubkey: Pubkey,
685 authority_signer_index: usize,
686 payer_signer_index: usize,
687 new_addresses: Vec<Pubkey>,
688) -> ProcessResult {
689 let authority_signer = config.signers[authority_signer_index];
690 let payer_signer = config.signers[payer_signer_index];
691
692 if new_addresses.is_empty() {
693 return Err("Lookup tables must be extended by at least one address".into());
694 }
695
696 let get_lookup_table_result = rpc_client
697 .get_account_with_commitment(&lookup_table_pubkey, config.commitment)
698 .await?;
699 let lookup_table_account = get_lookup_table_result.value.ok_or_else(|| {
700 format!("Lookup table account {lookup_table_pubkey} not found, was it already closed?")
701 })?;
702 if !address_lookup_table::program::check_id(&lookup_table_account.owner) {
703 return Err(format!(
704 "Lookup table account {lookup_table_pubkey} is not owned by the Address Lookup Table \
705 program",
706 )
707 .into());
708 }
709
710 let authority_address = authority_signer.pubkey();
711 let payer_address = payer_signer.pubkey();
712 let extend_lookup_table_ix = extend_lookup_table(
713 lookup_table_pubkey,
714 authority_address,
715 Some(payer_address),
716 new_addresses,
717 );
718
719 let blockhash = rpc_client.get_latest_blockhash().await?;
720 let mut tx = Transaction::new_unsigned(Message::new(
721 &[extend_lookup_table_ix],
722 Some(&config.signers[0].pubkey()),
723 ));
724
725 tx.try_sign(
726 &[config.signers[0], authority_signer, payer_signer],
727 blockhash,
728 )?;
729 let result = rpc_client
730 .send_and_confirm_transaction_with_spinner_and_config(
731 &tx,
732 config.commitment,
733 RpcSendTransactionConfig {
734 skip_preflight: false,
735 preflight_commitment: Some(config.commitment.commitment),
736 ..RpcSendTransactionConfig::default()
737 },
738 )
739 .await;
740 match result {
741 Err(err) => Err(format!("Extend failed: {err}").into()),
742 Ok(signature) => Ok(config.output_format.formatted_string(&CliSignature {
743 signature: signature.to_string(),
744 })),
745 }
746}
747
748pub const DEACTIVATE_LOOKUP_TABLE_WARNING: &str =
749 "WARNING! Once a lookup table is deactivated, it is no longer usable by transactions.
750Deactivated lookup tables may only be closed and cannot be recreated at the same address. To \
751 proceed with deactivation, rerun the `deactivate` command with the `--bypass-warning` flag";
752
753async fn process_deactivate_lookup_table(
754 rpc_client: &RpcClient,
755 config: &CliConfig<'_>,
756 lookup_table_pubkey: Pubkey,
757 authority_signer_index: usize,
758 bypass_warning: bool,
759) -> ProcessResult {
760 let authority_signer = config.signers[authority_signer_index];
761
762 let get_lookup_table_result = rpc_client
763 .get_account_with_commitment(&lookup_table_pubkey, config.commitment)
764 .await?;
765 let lookup_table_account = get_lookup_table_result.value.ok_or_else(|| {
766 format!("Lookup table account {lookup_table_pubkey} not found, was it already closed?")
767 })?;
768 if !address_lookup_table::program::check_id(&lookup_table_account.owner) {
769 return Err(format!(
770 "Lookup table account {lookup_table_pubkey} is not owned by the Address Lookup Table \
771 program",
772 )
773 .into());
774 }
775
776 if !bypass_warning {
777 return Err(String::from(DEACTIVATE_LOOKUP_TABLE_WARNING).into());
778 }
779
780 let authority_address = authority_signer.pubkey();
781 let deactivate_lookup_table_ix =
782 deactivate_lookup_table(lookup_table_pubkey, authority_address);
783
784 let blockhash = rpc_client.get_latest_blockhash().await?;
785 let mut tx = Transaction::new_unsigned(Message::new(
786 &[deactivate_lookup_table_ix],
787 Some(&config.signers[0].pubkey()),
788 ));
789
790 tx.try_sign(&[config.signers[0], authority_signer], blockhash)?;
791 let result = rpc_client
792 .send_and_confirm_transaction_with_spinner_and_config(
793 &tx,
794 config.commitment,
795 RpcSendTransactionConfig {
796 skip_preflight: false,
797 preflight_commitment: Some(config.commitment.commitment),
798 ..RpcSendTransactionConfig::default()
799 },
800 )
801 .await;
802 match result {
803 Err(err) => Err(format!("Deactivate failed: {err}").into()),
804 Ok(signature) => Ok(config.output_format.formatted_string(&CliSignature {
805 signature: signature.to_string(),
806 })),
807 }
808}
809
810async fn process_close_lookup_table(
811 rpc_client: &RpcClient,
812 config: &CliConfig<'_>,
813 lookup_table_pubkey: Pubkey,
814 authority_signer_index: usize,
815 recipient_pubkey: Pubkey,
816) -> ProcessResult {
817 let authority_signer = config.signers[authority_signer_index];
818
819 let get_lookup_table_result = rpc_client
820 .get_account_with_commitment(&lookup_table_pubkey, config.commitment)
821 .await?;
822 let lookup_table_account = get_lookup_table_result.value.ok_or_else(|| {
823 format!("Lookup table account {lookup_table_pubkey} not found, was it already closed?")
824 })?;
825 if !address_lookup_table::program::check_id(&lookup_table_account.owner) {
826 return Err(format!(
827 "Lookup table account {lookup_table_pubkey} is not owned by the Address Lookup Table \
828 program",
829 )
830 .into());
831 }
832
833 let lookup_table_account = AddressLookupTable::deserialize(&lookup_table_account.data)?;
834 if lookup_table_account.meta.deactivation_slot == u64::MAX {
835 return Err(format!(
836 "Lookup table account {lookup_table_pubkey} is not deactivated. Only deactivated \
837 lookup tables may be closed",
838 )
839 .into());
840 }
841
842 let authority_address = authority_signer.pubkey();
843 let close_lookup_table_ix =
844 close_lookup_table(lookup_table_pubkey, authority_address, recipient_pubkey);
845
846 let blockhash = rpc_client.get_latest_blockhash().await?;
847 let mut tx = Transaction::new_unsigned(Message::new(
848 &[close_lookup_table_ix],
849 Some(&config.signers[0].pubkey()),
850 ));
851
852 tx.try_sign(&[config.signers[0], authority_signer], blockhash)?;
853 let result = rpc_client
854 .send_and_confirm_transaction_with_spinner_and_config(
855 &tx,
856 config.commitment,
857 RpcSendTransactionConfig {
858 skip_preflight: false,
859 preflight_commitment: Some(config.commitment.commitment),
860 ..RpcSendTransactionConfig::default()
861 },
862 )
863 .await;
864 match result {
865 Err(err) => Err(format!("Close failed: {err}").into()),
866 Ok(signature) => Ok(config.output_format.formatted_string(&CliSignature {
867 signature: signature.to_string(),
868 })),
869 }
870}
871
872async fn process_show_lookup_table(
873 rpc_client: &RpcClient,
874 config: &CliConfig<'_>,
875 lookup_table_pubkey: Pubkey,
876) -> ProcessResult {
877 let get_lookup_table_result = rpc_client
878 .get_account_with_commitment(&lookup_table_pubkey, config.commitment)
879 .await?;
880 let lookup_table_account = get_lookup_table_result.value.ok_or_else(|| {
881 format!("Lookup table account {lookup_table_pubkey} not found, was it already closed?")
882 })?;
883 if !address_lookup_table::program::check_id(&lookup_table_account.owner) {
884 return Err(format!(
885 "Lookup table account {lookup_table_pubkey} is not owned by the Address Lookup Table \
886 program",
887 )
888 .into());
889 }
890
891 let lookup_table_account = AddressLookupTable::deserialize(&lookup_table_account.data)?;
892 Ok(config
893 .output_format
894 .formatted_string(&CliAddressLookupTable {
895 lookup_table_address: lookup_table_pubkey.to_string(),
896 authority: lookup_table_account
897 .meta
898 .authority
899 .as_ref()
900 .map(ToString::to_string),
901 deactivation_slot: lookup_table_account.meta.deactivation_slot,
902 last_extended_slot: lookup_table_account.meta.last_extended_slot,
903 addresses: lookup_table_account
904 .addresses
905 .iter()
906 .map(ToString::to_string)
907 .collect(),
908 }))
909}