1use {
2 crate::cli::{CliCommand, CliCommandInfo, CliConfig, CliError, ProcessResult},
3 clap::{App, AppSettings, Arg, ArgMatches, SubCommand},
4 solana_account::from_account,
5 solana_address_lookup_table_interface::{
6 self as address_lookup_table,
7 instruction::{
8 close_lookup_table, create_lookup_table, deactivate_lookup_table, extend_lookup_table,
9 freeze_lookup_table,
10 },
11 state::AddressLookupTable,
12 },
13 solana_clap_utils::{self, input_parsers::*, input_validators::*, keypair::*},
14 solana_cli_output::{CliAddressLookupTable, CliAddressLookupTableCreated, CliSignature},
15 solana_clock::Clock,
16 solana_commitment_config::CommitmentConfig,
17 solana_message::Message,
18 solana_pubkey::Pubkey,
19 solana_remote_wallet::remote_wallet::RemoteWalletManager,
20 solana_rpc_client::nonblocking::rpc_client::RpcClient,
21 solana_rpc_client_api::config::RpcSendTransactionConfig,
22 solana_sdk_ids::sysvar,
23 solana_signer::Signer,
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 = from_account(&clock_account).ok_or_else(|| {
562 CliError::RpcRequestError("Failed to deserialize clock sysvar".to_string())
563 })?;
564
565 let payer_address = payer_signer.pubkey();
566 let (create_lookup_table_ix, lookup_table_address) =
567 create_lookup_table(authority_address, payer_address, clock.slot);
568
569 let blockhash = rpc_client.get_latest_blockhash().await?;
570 let mut tx = Transaction::new_unsigned(Message::new(
571 &[create_lookup_table_ix],
572 Some(&config.signers[0].pubkey()),
573 ));
574
575 let keypairs: Vec<&dyn Signer> = vec![config.signers[0], payer_signer];
576 tx.try_sign(&keypairs, blockhash)?;
577 let result = rpc_client
578 .send_and_confirm_transaction_with_spinner_and_config(
579 &tx,
580 config.commitment,
581 RpcSendTransactionConfig {
582 skip_preflight: false,
583 preflight_commitment: Some(config.commitment.commitment),
584 ..RpcSendTransactionConfig::default()
585 },
586 )
587 .await;
588 match result {
589 Err(err) => Err(format!("Create failed: {err}").into()),
590 Ok(signature) => Ok(config
591 .output_format
592 .formatted_string(&CliAddressLookupTableCreated {
593 lookup_table_address: lookup_table_address.to_string(),
594 signature: signature.to_string(),
595 })),
596 }
597}
598
599pub const FREEZE_LOOKUP_TABLE_WARNING: &str =
600 "WARNING! Once a lookup table is frozen, it can never be modified or unfrozen again. To \
601 proceed with freezing, rerun the `freeze` command with the `--bypass-warning` flag";
602
603async fn process_freeze_lookup_table(
604 rpc_client: &RpcClient,
605 config: &CliConfig<'_>,
606 lookup_table_pubkey: Pubkey,
607 authority_signer_index: usize,
608 bypass_warning: bool,
609) -> ProcessResult {
610 let authority_signer = config.signers[authority_signer_index];
611
612 let get_lookup_table_result = rpc_client
613 .get_account_with_commitment(&lookup_table_pubkey, config.commitment)
614 .await?;
615 let lookup_table_account = get_lookup_table_result.value.ok_or_else(|| {
616 format!("Lookup table account {lookup_table_pubkey} not found, was it already closed?")
617 })?;
618 if !address_lookup_table::program::check_id(&lookup_table_account.owner) {
619 return Err(format!(
620 "Lookup table account {lookup_table_pubkey} is not owned by the Address Lookup Table \
621 program",
622 )
623 .into());
624 }
625
626 if !bypass_warning {
627 return Err(String::from(FREEZE_LOOKUP_TABLE_WARNING).into());
628 }
629
630 let authority_address = authority_signer.pubkey();
631 let freeze_lookup_table_ix = freeze_lookup_table(lookup_table_pubkey, authority_address);
632
633 let blockhash = rpc_client.get_latest_blockhash().await?;
634 let mut tx = Transaction::new_unsigned(Message::new(
635 &[freeze_lookup_table_ix],
636 Some(&config.signers[0].pubkey()),
637 ));
638
639 tx.try_sign(&[config.signers[0], authority_signer], blockhash)?;
640 let result = rpc_client
641 .send_and_confirm_transaction_with_spinner_and_config(
642 &tx,
643 config.commitment,
644 RpcSendTransactionConfig {
645 skip_preflight: false,
646 preflight_commitment: Some(config.commitment.commitment),
647 ..RpcSendTransactionConfig::default()
648 },
649 )
650 .await;
651 match result {
652 Err(err) => Err(format!("Freeze failed: {err}").into()),
653 Ok(signature) => Ok(config.output_format.formatted_string(&CliSignature {
654 signature: signature.to_string(),
655 })),
656 }
657}
658
659async fn process_extend_lookup_table(
660 rpc_client: &RpcClient,
661 config: &CliConfig<'_>,
662 lookup_table_pubkey: Pubkey,
663 authority_signer_index: usize,
664 payer_signer_index: usize,
665 new_addresses: Vec<Pubkey>,
666) -> ProcessResult {
667 let authority_signer = config.signers[authority_signer_index];
668 let payer_signer = config.signers[payer_signer_index];
669
670 if new_addresses.is_empty() {
671 return Err("Lookup tables must be extended by at least one address".into());
672 }
673
674 let get_lookup_table_result = rpc_client
675 .get_account_with_commitment(&lookup_table_pubkey, config.commitment)
676 .await?;
677 let lookup_table_account = get_lookup_table_result.value.ok_or_else(|| {
678 format!("Lookup table account {lookup_table_pubkey} not found, was it already closed?")
679 })?;
680 if !address_lookup_table::program::check_id(&lookup_table_account.owner) {
681 return Err(format!(
682 "Lookup table account {lookup_table_pubkey} is not owned by the Address Lookup Table \
683 program",
684 )
685 .into());
686 }
687
688 let authority_address = authority_signer.pubkey();
689 let payer_address = payer_signer.pubkey();
690 let extend_lookup_table_ix = extend_lookup_table(
691 lookup_table_pubkey,
692 authority_address,
693 Some(payer_address),
694 new_addresses,
695 );
696
697 let blockhash = rpc_client.get_latest_blockhash().await?;
698 let mut tx = Transaction::new_unsigned(Message::new(
699 &[extend_lookup_table_ix],
700 Some(&config.signers[0].pubkey()),
701 ));
702
703 tx.try_sign(
704 &[config.signers[0], authority_signer, payer_signer],
705 blockhash,
706 )?;
707 let result = rpc_client
708 .send_and_confirm_transaction_with_spinner_and_config(
709 &tx,
710 config.commitment,
711 RpcSendTransactionConfig {
712 skip_preflight: false,
713 preflight_commitment: Some(config.commitment.commitment),
714 ..RpcSendTransactionConfig::default()
715 },
716 )
717 .await;
718 match result {
719 Err(err) => Err(format!("Extend failed: {err}").into()),
720 Ok(signature) => Ok(config.output_format.formatted_string(&CliSignature {
721 signature: signature.to_string(),
722 })),
723 }
724}
725
726pub const DEACTIVATE_LOOKUP_TABLE_WARNING: &str =
727 "WARNING! Once a lookup table is deactivated, it is no longer usable by transactions.
728Deactivated lookup tables may only be closed and cannot be recreated at the same address. To \
729 proceed with deactivation, rerun the `deactivate` command with the `--bypass-warning` flag";
730
731async fn process_deactivate_lookup_table(
732 rpc_client: &RpcClient,
733 config: &CliConfig<'_>,
734 lookup_table_pubkey: Pubkey,
735 authority_signer_index: usize,
736 bypass_warning: bool,
737) -> ProcessResult {
738 let authority_signer = config.signers[authority_signer_index];
739
740 let get_lookup_table_result = rpc_client
741 .get_account_with_commitment(&lookup_table_pubkey, config.commitment)
742 .await?;
743 let lookup_table_account = get_lookup_table_result.value.ok_or_else(|| {
744 format!("Lookup table account {lookup_table_pubkey} not found, was it already closed?")
745 })?;
746 if !address_lookup_table::program::check_id(&lookup_table_account.owner) {
747 return Err(format!(
748 "Lookup table account {lookup_table_pubkey} is not owned by the Address Lookup Table \
749 program",
750 )
751 .into());
752 }
753
754 if !bypass_warning {
755 return Err(String::from(DEACTIVATE_LOOKUP_TABLE_WARNING).into());
756 }
757
758 let authority_address = authority_signer.pubkey();
759 let deactivate_lookup_table_ix =
760 deactivate_lookup_table(lookup_table_pubkey, authority_address);
761
762 let blockhash = rpc_client.get_latest_blockhash().await?;
763 let mut tx = Transaction::new_unsigned(Message::new(
764 &[deactivate_lookup_table_ix],
765 Some(&config.signers[0].pubkey()),
766 ));
767
768 tx.try_sign(&[config.signers[0], authority_signer], blockhash)?;
769 let result = rpc_client
770 .send_and_confirm_transaction_with_spinner_and_config(
771 &tx,
772 config.commitment,
773 RpcSendTransactionConfig {
774 skip_preflight: false,
775 preflight_commitment: Some(config.commitment.commitment),
776 ..RpcSendTransactionConfig::default()
777 },
778 )
779 .await;
780 match result {
781 Err(err) => Err(format!("Deactivate failed: {err}").into()),
782 Ok(signature) => Ok(config.output_format.formatted_string(&CliSignature {
783 signature: signature.to_string(),
784 })),
785 }
786}
787
788async fn process_close_lookup_table(
789 rpc_client: &RpcClient,
790 config: &CliConfig<'_>,
791 lookup_table_pubkey: Pubkey,
792 authority_signer_index: usize,
793 recipient_pubkey: Pubkey,
794) -> ProcessResult {
795 let authority_signer = config.signers[authority_signer_index];
796
797 let get_lookup_table_result = rpc_client
798 .get_account_with_commitment(&lookup_table_pubkey, config.commitment)
799 .await?;
800 let lookup_table_account = get_lookup_table_result.value.ok_or_else(|| {
801 format!("Lookup table account {lookup_table_pubkey} not found, was it already closed?")
802 })?;
803 if !address_lookup_table::program::check_id(&lookup_table_account.owner) {
804 return Err(format!(
805 "Lookup table account {lookup_table_pubkey} is not owned by the Address Lookup Table \
806 program",
807 )
808 .into());
809 }
810
811 let lookup_table_account = AddressLookupTable::deserialize(&lookup_table_account.data)?;
812 if lookup_table_account.meta.deactivation_slot == u64::MAX {
813 return Err(format!(
814 "Lookup table account {lookup_table_pubkey} is not deactivated. Only deactivated \
815 lookup tables may be closed",
816 )
817 .into());
818 }
819
820 let authority_address = authority_signer.pubkey();
821 let close_lookup_table_ix =
822 close_lookup_table(lookup_table_pubkey, authority_address, recipient_pubkey);
823
824 let blockhash = rpc_client.get_latest_blockhash().await?;
825 let mut tx = Transaction::new_unsigned(Message::new(
826 &[close_lookup_table_ix],
827 Some(&config.signers[0].pubkey()),
828 ));
829
830 tx.try_sign(&[config.signers[0], authority_signer], blockhash)?;
831 let result = rpc_client
832 .send_and_confirm_transaction_with_spinner_and_config(
833 &tx,
834 config.commitment,
835 RpcSendTransactionConfig {
836 skip_preflight: false,
837 preflight_commitment: Some(config.commitment.commitment),
838 ..RpcSendTransactionConfig::default()
839 },
840 )
841 .await;
842 match result {
843 Err(err) => Err(format!("Close failed: {err}").into()),
844 Ok(signature) => Ok(config.output_format.formatted_string(&CliSignature {
845 signature: signature.to_string(),
846 })),
847 }
848}
849
850async fn process_show_lookup_table(
851 rpc_client: &RpcClient,
852 config: &CliConfig<'_>,
853 lookup_table_pubkey: Pubkey,
854) -> ProcessResult {
855 let get_lookup_table_result = rpc_client
856 .get_account_with_commitment(&lookup_table_pubkey, config.commitment)
857 .await?;
858 let lookup_table_account = get_lookup_table_result.value.ok_or_else(|| {
859 format!("Lookup table account {lookup_table_pubkey} not found, was it already closed?")
860 })?;
861 if !address_lookup_table::program::check_id(&lookup_table_account.owner) {
862 return Err(format!(
863 "Lookup table account {lookup_table_pubkey} is not owned by the Address Lookup Table \
864 program",
865 )
866 .into());
867 }
868
869 let lookup_table_account = AddressLookupTable::deserialize(&lookup_table_account.data)?;
870 Ok(config
871 .output_format
872 .formatted_string(&CliAddressLookupTable {
873 lookup_table_address: lookup_table_pubkey.to_string(),
874 authority: lookup_table_account
875 .meta
876 .authority
877 .as_ref()
878 .map(ToString::to_string),
879 deactivation_slot: lookup_table_account.meta.deactivation_slot,
880 last_extended_slot: lookup_table_account.meta.last_extended_slot,
881 addresses: lookup_table_account
882 .addresses
883 .iter()
884 .map(ToString::to_string)
885 .collect(),
886 }))
887}