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