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