Skip to main content

solana_cli/
address_lookup_table.rs

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}