miraland_cli/
address_lookup_table.rs

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