1use {
2 crate::cli::{CliCommand, CliCommandInfo, CliConfig, CliError, ProcessResult},
3 clap::{App, AppSettings, Arg, ArgMatches, SubCommand},
4 solana_account::from_account,
5 solana_address_lookup_table_interface::{
6 self as address_lookup_table,
7 instruction::{
8 close_lookup_table, create_lookup_table, deactivate_lookup_table, extend_lookup_table,
9 freeze_lookup_table,
10 },
11 state::AddressLookupTable,
12 },
13 solana_clap_utils::{self, input_parsers::*, input_validators::*, keypair::*},
14 solana_cli_output::{CliAddressLookupTable, CliAddressLookupTableCreated, CliSignature},
15 solana_clock::Clock,
16 solana_commitment_config::CommitmentConfig,
17 solana_message::Message,
18 solana_pubkey::Pubkey,
19 solana_remote_wallet::remote_wallet::RemoteWalletManager,
20 solana_rpc_client::rpc_client::RpcClient,
21 solana_rpc_client_api::config::RpcSendTransactionConfig,
22 solana_sdk_ids::sysvar,
23 solana_signer::Signer,
24 solana_transaction::Transaction,
25 std::{rc::Rc, sync::Arc},
26};
27
28#[derive(Debug, PartialEq, Eq)]
29pub enum AddressLookupTableCliCommand {
30 CreateLookupTable {
31 authority_pubkey: Pubkey,
32 payer_signer_index: SignerIndex,
33 },
34 FreezeLookupTable {
35 lookup_table_pubkey: Pubkey,
36 authority_signer_index: SignerIndex,
37 bypass_warning: bool,
38 },
39 ExtendLookupTable {
40 lookup_table_pubkey: Pubkey,
41 authority_signer_index: SignerIndex,
42 payer_signer_index: SignerIndex,
43 new_addresses: Vec<Pubkey>,
44 },
45 DeactivateLookupTable {
46 lookup_table_pubkey: Pubkey,
47 authority_signer_index: SignerIndex,
48 bypass_warning: bool,
49 },
50 CloseLookupTable {
51 lookup_table_pubkey: Pubkey,
52 authority_signer_index: SignerIndex,
53 recipient_pubkey: Pubkey,
54 },
55 ShowLookupTable {
56 lookup_table_pubkey: Pubkey,
57 },
58}
59
60pub trait AddressLookupTableSubCommands {
61 fn address_lookup_table_subcommands(self) -> Self;
62}
63
64impl AddressLookupTableSubCommands for App<'_, '_> {
65 fn address_lookup_table_subcommands(self) -> Self {
66 self.subcommand(
67 SubCommand::with_name("address-lookup-table")
68 .about("Address lookup table management")
69 .setting(AppSettings::SubcommandRequiredElseHelp)
70 .subcommand(
71 SubCommand::with_name("create")
72 .about("Create a lookup table")
73 .arg(
74 Arg::with_name("authority")
75 .long("authority")
76 .alias("authority-signer")
77 .value_name("AUTHORITY_PUBKEY")
78 .takes_value(true)
79 .validator(is_pubkey_or_keypair)
80 .help(
81 "Lookup table authority address [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 fn process_address_lookup_table_subcommand(
473 rpc_client: Arc<RpcClient>,
474 config: &CliConfig,
475 subcommand: &AddressLookupTableCliCommand,
476) -> ProcessResult {
477 match subcommand {
478 AddressLookupTableCliCommand::CreateLookupTable {
479 authority_pubkey,
480 payer_signer_index,
481 } => {
482 process_create_lookup_table(&rpc_client, config, *authority_pubkey, *payer_signer_index)
483 }
484 AddressLookupTableCliCommand::FreezeLookupTable {
485 lookup_table_pubkey,
486 authority_signer_index,
487 bypass_warning,
488 } => process_freeze_lookup_table(
489 &rpc_client,
490 config,
491 *lookup_table_pubkey,
492 *authority_signer_index,
493 *bypass_warning,
494 ),
495 AddressLookupTableCliCommand::ExtendLookupTable {
496 lookup_table_pubkey,
497 authority_signer_index,
498 payer_signer_index,
499 new_addresses,
500 } => process_extend_lookup_table(
501 &rpc_client,
502 config,
503 *lookup_table_pubkey,
504 *authority_signer_index,
505 *payer_signer_index,
506 new_addresses.to_vec(),
507 ),
508 AddressLookupTableCliCommand::DeactivateLookupTable {
509 lookup_table_pubkey,
510 authority_signer_index,
511 bypass_warning,
512 } => process_deactivate_lookup_table(
513 &rpc_client,
514 config,
515 *lookup_table_pubkey,
516 *authority_signer_index,
517 *bypass_warning,
518 ),
519 AddressLookupTableCliCommand::CloseLookupTable {
520 lookup_table_pubkey,
521 authority_signer_index,
522 recipient_pubkey,
523 } => process_close_lookup_table(
524 &rpc_client,
525 config,
526 *lookup_table_pubkey,
527 *authority_signer_index,
528 *recipient_pubkey,
529 ),
530 AddressLookupTableCliCommand::ShowLookupTable {
531 lookup_table_pubkey,
532 } => process_show_lookup_table(&rpc_client, config, *lookup_table_pubkey),
533 }
534}
535
536fn process_create_lookup_table(
537 rpc_client: &RpcClient,
538 config: &CliConfig,
539 authority_address: Pubkey,
540 payer_signer_index: usize,
541) -> ProcessResult {
542 let payer_signer = config.signers[payer_signer_index];
543
544 let get_clock_result = rpc_client
545 .get_account_with_commitment(&sysvar::clock::id(), CommitmentConfig::finalized())?;
546 let clock_account = get_clock_result.value.expect("Clock account doesn't exist");
547 let clock: Clock = from_account(&clock_account).ok_or_else(|| {
548 CliError::RpcRequestError("Failed to deserialize clock sysvar".to_string())
549 })?;
550
551 let payer_address = payer_signer.pubkey();
552 let (create_lookup_table_ix, lookup_table_address) =
553 create_lookup_table(authority_address, payer_address, clock.slot);
554
555 let blockhash = rpc_client.get_latest_blockhash()?;
556 let mut tx = Transaction::new_unsigned(Message::new(
557 &[create_lookup_table_ix],
558 Some(&config.signers[0].pubkey()),
559 ));
560
561 let keypairs: Vec<&dyn Signer> = vec![config.signers[0], payer_signer];
562 tx.try_sign(&keypairs, blockhash)?;
563 let result = rpc_client.send_and_confirm_transaction_with_spinner_and_config(
564 &tx,
565 config.commitment,
566 RpcSendTransactionConfig {
567 skip_preflight: false,
568 preflight_commitment: Some(config.commitment.commitment),
569 ..RpcSendTransactionConfig::default()
570 },
571 );
572 match result {
573 Err(err) => Err(format!("Create failed: {err}").into()),
574 Ok(signature) => Ok(config
575 .output_format
576 .formatted_string(&CliAddressLookupTableCreated {
577 lookup_table_address: lookup_table_address.to_string(),
578 signature: signature.to_string(),
579 })),
580 }
581}
582
583pub const FREEZE_LOOKUP_TABLE_WARNING: &str =
584 "WARNING! Once a lookup table is frozen, it can never be modified or unfrozen again. To \
585 proceed with freezing, rerun the `freeze` command with the `--bypass-warning` flag";
586
587fn process_freeze_lookup_table(
588 rpc_client: &RpcClient,
589 config: &CliConfig,
590 lookup_table_pubkey: Pubkey,
591 authority_signer_index: usize,
592 bypass_warning: bool,
593) -> ProcessResult {
594 let authority_signer = config.signers[authority_signer_index];
595
596 let get_lookup_table_result =
597 rpc_client.get_account_with_commitment(&lookup_table_pubkey, config.commitment)?;
598 let lookup_table_account = get_lookup_table_result.value.ok_or_else(|| {
599 format!("Lookup table account {lookup_table_pubkey} not found, was it already closed?")
600 })?;
601 if !address_lookup_table::program::check_id(&lookup_table_account.owner) {
602 return Err(format!(
603 "Lookup table account {lookup_table_pubkey} is not owned by the Address Lookup Table \
604 program",
605 )
606 .into());
607 }
608
609 if !bypass_warning {
610 return Err(String::from(FREEZE_LOOKUP_TABLE_WARNING).into());
611 }
612
613 let authority_address = authority_signer.pubkey();
614 let freeze_lookup_table_ix = freeze_lookup_table(lookup_table_pubkey, authority_address);
615
616 let blockhash = rpc_client.get_latest_blockhash()?;
617 let mut tx = Transaction::new_unsigned(Message::new(
618 &[freeze_lookup_table_ix],
619 Some(&config.signers[0].pubkey()),
620 ));
621
622 tx.try_sign(&[config.signers[0], authority_signer], blockhash)?;
623 let result = rpc_client.send_and_confirm_transaction_with_spinner_and_config(
624 &tx,
625 config.commitment,
626 RpcSendTransactionConfig {
627 skip_preflight: false,
628 preflight_commitment: Some(config.commitment.commitment),
629 ..RpcSendTransactionConfig::default()
630 },
631 );
632 match result {
633 Err(err) => Err(format!("Freeze failed: {err}").into()),
634 Ok(signature) => Ok(config.output_format.formatted_string(&CliSignature {
635 signature: signature.to_string(),
636 })),
637 }
638}
639
640fn process_extend_lookup_table(
641 rpc_client: &RpcClient,
642 config: &CliConfig,
643 lookup_table_pubkey: Pubkey,
644 authority_signer_index: usize,
645 payer_signer_index: usize,
646 new_addresses: Vec<Pubkey>,
647) -> ProcessResult {
648 let authority_signer = config.signers[authority_signer_index];
649 let payer_signer = config.signers[payer_signer_index];
650
651 if new_addresses.is_empty() {
652 return Err("Lookup tables must be extended by at least one address".into());
653 }
654
655 let get_lookup_table_result =
656 rpc_client.get_account_with_commitment(&lookup_table_pubkey, config.commitment)?;
657 let lookup_table_account = get_lookup_table_result.value.ok_or_else(|| {
658 format!("Lookup table account {lookup_table_pubkey} not found, was it already closed?")
659 })?;
660 if !address_lookup_table::program::check_id(&lookup_table_account.owner) {
661 return Err(format!(
662 "Lookup table account {lookup_table_pubkey} is not owned by the Address Lookup Table \
663 program",
664 )
665 .into());
666 }
667
668 let authority_address = authority_signer.pubkey();
669 let payer_address = payer_signer.pubkey();
670 let extend_lookup_table_ix = extend_lookup_table(
671 lookup_table_pubkey,
672 authority_address,
673 Some(payer_address),
674 new_addresses,
675 );
676
677 let blockhash = rpc_client.get_latest_blockhash()?;
678 let mut tx = Transaction::new_unsigned(Message::new(
679 &[extend_lookup_table_ix],
680 Some(&config.signers[0].pubkey()),
681 ));
682
683 tx.try_sign(
684 &[config.signers[0], authority_signer, payer_signer],
685 blockhash,
686 )?;
687 let result = rpc_client.send_and_confirm_transaction_with_spinner_and_config(
688 &tx,
689 config.commitment,
690 RpcSendTransactionConfig {
691 skip_preflight: false,
692 preflight_commitment: Some(config.commitment.commitment),
693 ..RpcSendTransactionConfig::default()
694 },
695 );
696 match result {
697 Err(err) => Err(format!("Extend failed: {err}").into()),
698 Ok(signature) => Ok(config.output_format.formatted_string(&CliSignature {
699 signature: signature.to_string(),
700 })),
701 }
702}
703
704pub const DEACTIVATE_LOOKUP_TABLE_WARNING: &str =
705 "WARNING! Once a lookup table is deactivated, it is no longer usable by transactions.
706Deactivated lookup tables may only be closed and cannot be recreated at the same address. To \
707 proceed with deactivation, rerun the `deactivate` command with the `--bypass-warning` flag";
708
709fn process_deactivate_lookup_table(
710 rpc_client: &RpcClient,
711 config: &CliConfig,
712 lookup_table_pubkey: Pubkey,
713 authority_signer_index: usize,
714 bypass_warning: bool,
715) -> ProcessResult {
716 let authority_signer = config.signers[authority_signer_index];
717
718 let get_lookup_table_result =
719 rpc_client.get_account_with_commitment(&lookup_table_pubkey, config.commitment)?;
720 let lookup_table_account = get_lookup_table_result.value.ok_or_else(|| {
721 format!("Lookup table account {lookup_table_pubkey} not found, was it already closed?")
722 })?;
723 if !address_lookup_table::program::check_id(&lookup_table_account.owner) {
724 return Err(format!(
725 "Lookup table account {lookup_table_pubkey} is not owned by the Address Lookup Table \
726 program",
727 )
728 .into());
729 }
730
731 if !bypass_warning {
732 return Err(String::from(DEACTIVATE_LOOKUP_TABLE_WARNING).into());
733 }
734
735 let authority_address = authority_signer.pubkey();
736 let deactivate_lookup_table_ix =
737 deactivate_lookup_table(lookup_table_pubkey, authority_address);
738
739 let blockhash = rpc_client.get_latest_blockhash()?;
740 let mut tx = Transaction::new_unsigned(Message::new(
741 &[deactivate_lookup_table_ix],
742 Some(&config.signers[0].pubkey()),
743 ));
744
745 tx.try_sign(&[config.signers[0], authority_signer], blockhash)?;
746 let result = rpc_client.send_and_confirm_transaction_with_spinner_and_config(
747 &tx,
748 config.commitment,
749 RpcSendTransactionConfig {
750 skip_preflight: false,
751 preflight_commitment: Some(config.commitment.commitment),
752 ..RpcSendTransactionConfig::default()
753 },
754 );
755 match result {
756 Err(err) => Err(format!("Deactivate failed: {err}").into()),
757 Ok(signature) => Ok(config.output_format.formatted_string(&CliSignature {
758 signature: signature.to_string(),
759 })),
760 }
761}
762
763fn process_close_lookup_table(
764 rpc_client: &RpcClient,
765 config: &CliConfig,
766 lookup_table_pubkey: Pubkey,
767 authority_signer_index: usize,
768 recipient_pubkey: Pubkey,
769) -> ProcessResult {
770 let authority_signer = config.signers[authority_signer_index];
771
772 let get_lookup_table_result =
773 rpc_client.get_account_with_commitment(&lookup_table_pubkey, config.commitment)?;
774 let lookup_table_account = get_lookup_table_result.value.ok_or_else(|| {
775 format!("Lookup table account {lookup_table_pubkey} not found, was it already closed?")
776 })?;
777 if !address_lookup_table::program::check_id(&lookup_table_account.owner) {
778 return Err(format!(
779 "Lookup table account {lookup_table_pubkey} is not owned by the Address Lookup Table \
780 program",
781 )
782 .into());
783 }
784
785 let lookup_table_account = AddressLookupTable::deserialize(&lookup_table_account.data)?;
786 if lookup_table_account.meta.deactivation_slot == u64::MAX {
787 return Err(format!(
788 "Lookup table account {lookup_table_pubkey} is not deactivated. Only deactivated \
789 lookup tables may be closed",
790 )
791 .into());
792 }
793
794 let authority_address = authority_signer.pubkey();
795 let close_lookup_table_ix =
796 close_lookup_table(lookup_table_pubkey, authority_address, recipient_pubkey);
797
798 let blockhash = rpc_client.get_latest_blockhash()?;
799 let mut tx = Transaction::new_unsigned(Message::new(
800 &[close_lookup_table_ix],
801 Some(&config.signers[0].pubkey()),
802 ));
803
804 tx.try_sign(&[config.signers[0], authority_signer], blockhash)?;
805 let result = rpc_client.send_and_confirm_transaction_with_spinner_and_config(
806 &tx,
807 config.commitment,
808 RpcSendTransactionConfig {
809 skip_preflight: false,
810 preflight_commitment: Some(config.commitment.commitment),
811 ..RpcSendTransactionConfig::default()
812 },
813 );
814 match result {
815 Err(err) => Err(format!("Close failed: {err}").into()),
816 Ok(signature) => Ok(config.output_format.formatted_string(&CliSignature {
817 signature: signature.to_string(),
818 })),
819 }
820}
821
822fn process_show_lookup_table(
823 rpc_client: &RpcClient,
824 config: &CliConfig,
825 lookup_table_pubkey: Pubkey,
826) -> ProcessResult {
827 let get_lookup_table_result =
828 rpc_client.get_account_with_commitment(&lookup_table_pubkey, config.commitment)?;
829 let lookup_table_account = get_lookup_table_result.value.ok_or_else(|| {
830 format!("Lookup table account {lookup_table_pubkey} not found, was it already closed?")
831 })?;
832 if !address_lookup_table::program::check_id(&lookup_table_account.owner) {
833 return Err(format!(
834 "Lookup table account {lookup_table_pubkey} is not owned by the Address Lookup Table \
835 program",
836 )
837 .into());
838 }
839
840 let lookup_table_account = AddressLookupTable::deserialize(&lookup_table_account.data)?;
841 Ok(config
842 .output_format
843 .formatted_string(&CliAddressLookupTable {
844 lookup_table_address: lookup_table_pubkey.to_string(),
845 authority: lookup_table_account
846 .meta
847 .authority
848 .as_ref()
849 .map(ToString::to_string),
850 deactivation_slot: lookup_table_account.meta.deactivation_slot,
851 last_extended_slot: lookup_table_account.meta.last_extended_slot,
852 addresses: lookup_table_account
853 .addresses
854 .iter()
855 .map(ToString::to_string)
856 .collect(),
857 }))
858}