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}