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