solana_cli/
address_lookup_table.rs

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::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 \
82                                    [default: the default 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 \
117                                    [default: the default configured 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 \
147                                    [default: the default configured 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 \
191                                    [default: the default configured 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 \
231                                    [default: the default configured 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 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        }
484        AddressLookupTableCliCommand::FreezeLookupTable {
485            lookup_table_pubkey,
486            authority_signer_index,
487            bypass_warning,
488        } => process_freeze_lookup_table(
489            &rpc_client,
490            config,
491            *lookup_table_pubkey,
492            *authority_signer_index,
493            *bypass_warning,
494        ),
495        AddressLookupTableCliCommand::ExtendLookupTable {
496            lookup_table_pubkey,
497            authority_signer_index,
498            payer_signer_index,
499            new_addresses,
500        } => process_extend_lookup_table(
501            &rpc_client,
502            config,
503            *lookup_table_pubkey,
504            *authority_signer_index,
505            *payer_signer_index,
506            new_addresses.to_vec(),
507        ),
508        AddressLookupTableCliCommand::DeactivateLookupTable {
509            lookup_table_pubkey,
510            authority_signer_index,
511            bypass_warning,
512        } => process_deactivate_lookup_table(
513            &rpc_client,
514            config,
515            *lookup_table_pubkey,
516            *authority_signer_index,
517            *bypass_warning,
518        ),
519        AddressLookupTableCliCommand::CloseLookupTable {
520            lookup_table_pubkey,
521            authority_signer_index,
522            recipient_pubkey,
523        } => process_close_lookup_table(
524            &rpc_client,
525            config,
526            *lookup_table_pubkey,
527            *authority_signer_index,
528            *recipient_pubkey,
529        ),
530        AddressLookupTableCliCommand::ShowLookupTable {
531            lookup_table_pubkey,
532        } => process_show_lookup_table(&rpc_client, config, *lookup_table_pubkey),
533    }
534}
535
536fn process_create_lookup_table(
537    rpc_client: &RpcClient,
538    config: &CliConfig,
539    authority_address: Pubkey,
540    payer_signer_index: usize,
541) -> ProcessResult {
542    let payer_signer = config.signers[payer_signer_index];
543
544    let get_clock_result = rpc_client
545        .get_account_with_commitment(&sysvar::clock::id(), CommitmentConfig::finalized())?;
546    let clock_account = get_clock_result.value.expect("Clock account doesn't exist");
547    let clock: Clock = from_account(&clock_account).ok_or_else(|| {
548        CliError::RpcRequestError("Failed to deserialize clock sysvar".to_string())
549    })?;
550
551    let payer_address = payer_signer.pubkey();
552    let (create_lookup_table_ix, lookup_table_address) =
553        create_lookup_table(authority_address, payer_address, clock.slot);
554
555    let blockhash = rpc_client.get_latest_blockhash()?;
556    let mut tx = Transaction::new_unsigned(Message::new(
557        &[create_lookup_table_ix],
558        Some(&config.signers[0].pubkey()),
559    ));
560
561    let keypairs: Vec<&dyn Signer> = vec![config.signers[0], payer_signer];
562    tx.try_sign(&keypairs, blockhash)?;
563    let result = rpc_client.send_and_confirm_transaction_with_spinner_and_config(
564        &tx,
565        config.commitment,
566        RpcSendTransactionConfig {
567            skip_preflight: false,
568            preflight_commitment: Some(config.commitment.commitment),
569            ..RpcSendTransactionConfig::default()
570        },
571    );
572    match result {
573        Err(err) => Err(format!("Create failed: {err}").into()),
574        Ok(signature) => Ok(config
575            .output_format
576            .formatted_string(&CliAddressLookupTableCreated {
577                lookup_table_address: lookup_table_address.to_string(),
578                signature: signature.to_string(),
579            })),
580    }
581}
582
583pub const FREEZE_LOOKUP_TABLE_WARNING: &str =
584    "WARNING! Once a lookup table is frozen, it can never be modified or unfrozen again. To \
585     proceed with freezing, rerun the `freeze` command with the `--bypass-warning` flag";
586
587fn process_freeze_lookup_table(
588    rpc_client: &RpcClient,
589    config: &CliConfig,
590    lookup_table_pubkey: Pubkey,
591    authority_signer_index: usize,
592    bypass_warning: bool,
593) -> ProcessResult {
594    let authority_signer = config.signers[authority_signer_index];
595
596    let get_lookup_table_result =
597        rpc_client.get_account_with_commitment(&lookup_table_pubkey, config.commitment)?;
598    let lookup_table_account = get_lookup_table_result.value.ok_or_else(|| {
599        format!("Lookup table account {lookup_table_pubkey} not found, was it already closed?")
600    })?;
601    if !address_lookup_table::program::check_id(&lookup_table_account.owner) {
602        return Err(format!(
603            "Lookup table account {lookup_table_pubkey} is not owned by the Address Lookup Table \
604             program",
605        )
606        .into());
607    }
608
609    if !bypass_warning {
610        return Err(String::from(FREEZE_LOOKUP_TABLE_WARNING).into());
611    }
612
613    let authority_address = authority_signer.pubkey();
614    let freeze_lookup_table_ix = freeze_lookup_table(lookup_table_pubkey, authority_address);
615
616    let blockhash = rpc_client.get_latest_blockhash()?;
617    let mut tx = Transaction::new_unsigned(Message::new(
618        &[freeze_lookup_table_ix],
619        Some(&config.signers[0].pubkey()),
620    ));
621
622    tx.try_sign(&[config.signers[0], authority_signer], blockhash)?;
623    let result = rpc_client.send_and_confirm_transaction_with_spinner_and_config(
624        &tx,
625        config.commitment,
626        RpcSendTransactionConfig {
627            skip_preflight: false,
628            preflight_commitment: Some(config.commitment.commitment),
629            ..RpcSendTransactionConfig::default()
630        },
631    );
632    match result {
633        Err(err) => Err(format!("Freeze failed: {err}").into()),
634        Ok(signature) => Ok(config.output_format.formatted_string(&CliSignature {
635            signature: signature.to_string(),
636        })),
637    }
638}
639
640fn process_extend_lookup_table(
641    rpc_client: &RpcClient,
642    config: &CliConfig,
643    lookup_table_pubkey: Pubkey,
644    authority_signer_index: usize,
645    payer_signer_index: usize,
646    new_addresses: Vec<Pubkey>,
647) -> ProcessResult {
648    let authority_signer = config.signers[authority_signer_index];
649    let payer_signer = config.signers[payer_signer_index];
650
651    if new_addresses.is_empty() {
652        return Err("Lookup tables must be extended by at least one address".into());
653    }
654
655    let get_lookup_table_result =
656        rpc_client.get_account_with_commitment(&lookup_table_pubkey, config.commitment)?;
657    let lookup_table_account = get_lookup_table_result.value.ok_or_else(|| {
658        format!("Lookup table account {lookup_table_pubkey} not found, was it already closed?")
659    })?;
660    if !address_lookup_table::program::check_id(&lookup_table_account.owner) {
661        return Err(format!(
662            "Lookup table account {lookup_table_pubkey} is not owned by the Address Lookup Table \
663             program",
664        )
665        .into());
666    }
667
668    let authority_address = authority_signer.pubkey();
669    let payer_address = payer_signer.pubkey();
670    let extend_lookup_table_ix = extend_lookup_table(
671        lookup_table_pubkey,
672        authority_address,
673        Some(payer_address),
674        new_addresses,
675    );
676
677    let blockhash = rpc_client.get_latest_blockhash()?;
678    let mut tx = Transaction::new_unsigned(Message::new(
679        &[extend_lookup_table_ix],
680        Some(&config.signers[0].pubkey()),
681    ));
682
683    tx.try_sign(&[config.signers[0], authority_signer], blockhash)?;
684    let result = rpc_client.send_and_confirm_transaction_with_spinner_and_config(
685        &tx,
686        config.commitment,
687        RpcSendTransactionConfig {
688            skip_preflight: false,
689            preflight_commitment: Some(config.commitment.commitment),
690            ..RpcSendTransactionConfig::default()
691        },
692    );
693    match result {
694        Err(err) => Err(format!("Extend failed: {err}").into()),
695        Ok(signature) => Ok(config.output_format.formatted_string(&CliSignature {
696            signature: signature.to_string(),
697        })),
698    }
699}
700
701pub const DEACTIVATE_LOOKUP_TABLE_WARNING: &str =
702    "WARNING! Once a lookup table is deactivated, it is no longer usable by transactions.
703Deactivated lookup tables may only be closed and cannot be recreated at the same address. To \
704     proceed with deactivation, rerun the `deactivate` command with the `--bypass-warning` flag";
705
706fn process_deactivate_lookup_table(
707    rpc_client: &RpcClient,
708    config: &CliConfig,
709    lookup_table_pubkey: Pubkey,
710    authority_signer_index: usize,
711    bypass_warning: bool,
712) -> ProcessResult {
713    let authority_signer = config.signers[authority_signer_index];
714
715    let get_lookup_table_result =
716        rpc_client.get_account_with_commitment(&lookup_table_pubkey, config.commitment)?;
717    let lookup_table_account = get_lookup_table_result.value.ok_or_else(|| {
718        format!("Lookup table account {lookup_table_pubkey} not found, was it already closed?")
719    })?;
720    if !address_lookup_table::program::check_id(&lookup_table_account.owner) {
721        return Err(format!(
722            "Lookup table account {lookup_table_pubkey} is not owned by the Address Lookup Table \
723             program",
724        )
725        .into());
726    }
727
728    if !bypass_warning {
729        return Err(String::from(DEACTIVATE_LOOKUP_TABLE_WARNING).into());
730    }
731
732    let authority_address = authority_signer.pubkey();
733    let deactivate_lookup_table_ix =
734        deactivate_lookup_table(lookup_table_pubkey, authority_address);
735
736    let blockhash = rpc_client.get_latest_blockhash()?;
737    let mut tx = Transaction::new_unsigned(Message::new(
738        &[deactivate_lookup_table_ix],
739        Some(&config.signers[0].pubkey()),
740    ));
741
742    tx.try_sign(&[config.signers[0], authority_signer], blockhash)?;
743    let result = rpc_client.send_and_confirm_transaction_with_spinner_and_config(
744        &tx,
745        config.commitment,
746        RpcSendTransactionConfig {
747            skip_preflight: false,
748            preflight_commitment: Some(config.commitment.commitment),
749            ..RpcSendTransactionConfig::default()
750        },
751    );
752    match result {
753        Err(err) => Err(format!("Deactivate failed: {err}").into()),
754        Ok(signature) => Ok(config.output_format.formatted_string(&CliSignature {
755            signature: signature.to_string(),
756        })),
757    }
758}
759
760fn process_close_lookup_table(
761    rpc_client: &RpcClient,
762    config: &CliConfig,
763    lookup_table_pubkey: Pubkey,
764    authority_signer_index: usize,
765    recipient_pubkey: Pubkey,
766) -> ProcessResult {
767    let authority_signer = config.signers[authority_signer_index];
768
769    let get_lookup_table_result =
770        rpc_client.get_account_with_commitment(&lookup_table_pubkey, config.commitment)?;
771    let lookup_table_account = get_lookup_table_result.value.ok_or_else(|| {
772        format!("Lookup table account {lookup_table_pubkey} not found, was it already closed?")
773    })?;
774    if !address_lookup_table::program::check_id(&lookup_table_account.owner) {
775        return Err(format!(
776            "Lookup table account {lookup_table_pubkey} is not owned by the Address Lookup Table \
777             program",
778        )
779        .into());
780    }
781
782    let lookup_table_account = AddressLookupTable::deserialize(&lookup_table_account.data)?;
783    if lookup_table_account.meta.deactivation_slot == u64::MAX {
784        return Err(format!(
785            "Lookup table account {lookup_table_pubkey} is not deactivated. Only deactivated \
786             lookup tables may be closed",
787        )
788        .into());
789    }
790
791    let authority_address = authority_signer.pubkey();
792    let close_lookup_table_ix =
793        close_lookup_table(lookup_table_pubkey, authority_address, recipient_pubkey);
794
795    let blockhash = rpc_client.get_latest_blockhash()?;
796    let mut tx = Transaction::new_unsigned(Message::new(
797        &[close_lookup_table_ix],
798        Some(&config.signers[0].pubkey()),
799    ));
800
801    tx.try_sign(&[config.signers[0], authority_signer], blockhash)?;
802    let result = rpc_client.send_and_confirm_transaction_with_spinner_and_config(
803        &tx,
804        config.commitment,
805        RpcSendTransactionConfig {
806            skip_preflight: false,
807            preflight_commitment: Some(config.commitment.commitment),
808            ..RpcSendTransactionConfig::default()
809        },
810    );
811    match result {
812        Err(err) => Err(format!("Close failed: {err}").into()),
813        Ok(signature) => Ok(config.output_format.formatted_string(&CliSignature {
814            signature: signature.to_string(),
815        })),
816    }
817}
818
819fn process_show_lookup_table(
820    rpc_client: &RpcClient,
821    config: &CliConfig,
822    lookup_table_pubkey: Pubkey,
823) -> ProcessResult {
824    let get_lookup_table_result =
825        rpc_client.get_account_with_commitment(&lookup_table_pubkey, config.commitment)?;
826    let lookup_table_account = get_lookup_table_result.value.ok_or_else(|| {
827        format!("Lookup table account {lookup_table_pubkey} not found, was it already closed?")
828    })?;
829    if !address_lookup_table::program::check_id(&lookup_table_account.owner) {
830        return Err(format!(
831            "Lookup table account {lookup_table_pubkey} is not owned by the Address Lookup Table \
832             program",
833        )
834        .into());
835    }
836
837    let lookup_table_account = AddressLookupTable::deserialize(&lookup_table_account.data)?;
838    Ok(config
839        .output_format
840        .formatted_string(&CliAddressLookupTable {
841            lookup_table_address: lookup_table_pubkey.to_string(),
842            authority: lookup_table_account
843                .meta
844                .authority
845                .as_ref()
846                .map(ToString::to_string),
847            deactivation_slot: lookup_table_account.meta.deactivation_slot,
848            last_extended_slot: lookup_table_account.meta.last_extended_slot,
849            addresses: lookup_table_account
850                .addresses
851                .iter()
852                .map(ToString::to_string)
853                .collect(),
854        }))
855}