|
10 | 10 | spend_utils::{resolve_spend_tx_and_check_account_balances, SpendAmount},
|
11 | 11 | },
|
12 | 12 | clap::{value_t_or_exit, App, Arg, ArgMatches, SubCommand},
|
| 13 | + hex::FromHex, |
13 | 14 | solana_account_decoder::{UiAccount, UiAccountEncoding},
|
14 | 15 | solana_clap_utils::{
|
15 | 16 | compute_unit_price::{compute_unit_price_arg, COMPUTE_UNIT_PRICE_ARG},
|
|
23 | 24 | },
|
24 | 25 | solana_cli_output::{
|
25 | 26 | display::{build_balance_message, BuildBalanceMessageConfig},
|
26 |
| - return_signers_with_config, CliAccount, CliBalance, CliSignatureVerificationStatus, |
27 |
| - CliTransaction, CliTransactionConfirmation, OutputFormat, ReturnSignersConfig, |
| 27 | + return_signers_with_config, CliAccount, CliBalance, CliFindProgramDerivedAddress, |
| 28 | + CliSignatureVerificationStatus, CliTransaction, CliTransactionConfirmation, OutputFormat, |
| 29 | + ReturnSignersConfig, |
28 | 30 | },
|
29 | 31 | solana_client::{
|
30 | 32 | blockhash_query::BlockhashQuery, nonce_utils, rpc_client::RpcClient,
|
|
45 | 47 | EncodableWithMeta, EncodedConfirmedTransactionWithStatusMeta, EncodedTransaction,
|
46 | 48 | TransactionBinaryEncoding, UiTransactionEncoding,
|
47 | 49 | },
|
48 |
| - std::{fmt::Write as FmtWrite, fs::File, io::Write, sync::Arc}, |
| 50 | + std::{fmt::Write as FmtWrite, fs::File, io::Write, str::FromStr, sync::Arc}, |
49 | 51 | };
|
50 | 52 |
|
51 | 53 | pub trait WalletSubCommands {
|
@@ -150,7 +152,10 @@ impl WalletSubCommands for App<'_, '_> {
|
150 | 152 | )
|
151 | 153 | .subcommand(
|
152 | 154 | SubCommand::with_name("create-address-with-seed")
|
153 |
| - .about("Generate a derived account address with a seed") |
| 155 | + .about( |
| 156 | + "Generate a derived account address with a seed. \ |
| 157 | + For program derived addresses (PDAs), use the find-program-derived-address command instead" |
| 158 | + ) |
154 | 159 | .arg(
|
155 | 160 | Arg::with_name("seed")
|
156 | 161 | .index(1)
|
@@ -179,6 +184,37 @@ impl WalletSubCommands for App<'_, '_> {
|
179 | 184 | "From (base) key, [default: cli config keypair]. "),
|
180 | 185 | ),
|
181 | 186 | )
|
| 187 | + .subcommand( |
| 188 | + SubCommand::with_name("find-program-derived-address") |
| 189 | + .about("Generate a program derived account address with a seed") |
| 190 | + .arg( |
| 191 | + Arg::with_name("program_id") |
| 192 | + .index(1) |
| 193 | + .value_name("PROGRAM_ID") |
| 194 | + .takes_value(true) |
| 195 | + .required(true) |
| 196 | + .help( |
| 197 | + "The program_id that the address will ultimately be used for, \n\ |
| 198 | + or one of NONCE, STAKE, and VOTE keywords", |
| 199 | + ), |
| 200 | + ) |
| 201 | + .arg( |
| 202 | + Arg::with_name("seeds") |
| 203 | + .min_values(0) |
| 204 | + .value_name("SEED") |
| 205 | + .takes_value(true) |
| 206 | + .validator(is_structured_seed) |
| 207 | + .help( |
| 208 | + "The seeds. \n\ |
| 209 | + Each one must match the pattern PREFIX:VALUE. \n\ |
| 210 | + PREFIX can be one of [string, pubkey, hex, u8] \n\ |
| 211 | + or matches the pattern [u,i][16,32,64,128][le,be] (for example u64le) for number values \n\ |
| 212 | + [u,i] - represents whether the number is unsigned or signed, \n\ |
| 213 | + [16,32,64,128] - represents the bit length, and \n\ |
| 214 | + [le,be] - represents the byte order - little endian or big endian" |
| 215 | + ), |
| 216 | + ), |
| 217 | + ) |
182 | 218 | .subcommand(
|
183 | 219 | SubCommand::with_name("decode-transaction")
|
184 | 220 | .about("Decode a serialized transaction")
|
@@ -392,6 +428,52 @@ pub fn parse_create_address_with_seed(
|
392 | 428 | })
|
393 | 429 | }
|
394 | 430 |
|
| 431 | +pub fn parse_find_program_derived_address( |
| 432 | + matches: &ArgMatches<'_>, |
| 433 | +) -> Result<CliCommandInfo, CliError> { |
| 434 | + let program_id = resolve_derived_address_program_id(matches, "program_id") |
| 435 | + .ok_or_else(|| CliError::BadParameter("PROGRAM_ID".to_string()))?; |
| 436 | + let seeds = matches |
| 437 | + .values_of("seeds") |
| 438 | + .map(|seeds| { |
| 439 | + seeds |
| 440 | + .map(|value| { |
| 441 | + let (prefix, value) = value.split_once(':').unwrap(); |
| 442 | + match prefix { |
| 443 | + "pubkey" => Pubkey::from_str(value).unwrap().to_bytes().to_vec(), |
| 444 | + "string" => value.as_bytes().to_vec(), |
| 445 | + "hex" => Vec::<u8>::from_hex(value).unwrap(), |
| 446 | + "u8" => u8::from_str(value).unwrap().to_le_bytes().to_vec(), |
| 447 | + "u16le" => u16::from_str(value).unwrap().to_le_bytes().to_vec(), |
| 448 | + "u32le" => u32::from_str(value).unwrap().to_le_bytes().to_vec(), |
| 449 | + "u64le" => u64::from_str(value).unwrap().to_le_bytes().to_vec(), |
| 450 | + "u128le" => u128::from_str(value).unwrap().to_le_bytes().to_vec(), |
| 451 | + "i16le" => i16::from_str(value).unwrap().to_le_bytes().to_vec(), |
| 452 | + "i32le" => i32::from_str(value).unwrap().to_le_bytes().to_vec(), |
| 453 | + "i64le" => i64::from_str(value).unwrap().to_le_bytes().to_vec(), |
| 454 | + "i128le" => i128::from_str(value).unwrap().to_le_bytes().to_vec(), |
| 455 | + "u16be" => u16::from_str(value).unwrap().to_be_bytes().to_vec(), |
| 456 | + "u32be" => u32::from_str(value).unwrap().to_be_bytes().to_vec(), |
| 457 | + "u64be" => u64::from_str(value).unwrap().to_be_bytes().to_vec(), |
| 458 | + "u128be" => u128::from_str(value).unwrap().to_be_bytes().to_vec(), |
| 459 | + "i16be" => i16::from_str(value).unwrap().to_be_bytes().to_vec(), |
| 460 | + "i32be" => i32::from_str(value).unwrap().to_be_bytes().to_vec(), |
| 461 | + "i64be" => i64::from_str(value).unwrap().to_be_bytes().to_vec(), |
| 462 | + "i128be" => i128::from_str(value).unwrap().to_be_bytes().to_vec(), |
| 463 | + // Must be unreachable due to arg validator |
| 464 | + _ => unreachable!("parse_find_program_derived_address: {prefix}:{value}"), |
| 465 | + } |
| 466 | + }) |
| 467 | + .collect::<Vec<_>>() |
| 468 | + }) |
| 469 | + .unwrap_or_default(); |
| 470 | + |
| 471 | + Ok(CliCommandInfo { |
| 472 | + command: CliCommand::FindProgramDerivedAddress { seeds, program_id }, |
| 473 | + signers: vec![], |
| 474 | + }) |
| 475 | +} |
| 476 | + |
395 | 477 | pub fn parse_transfer(
|
396 | 478 | matches: &ArgMatches<'_>,
|
397 | 479 | default_signer: &DefaultSigner,
|
@@ -658,6 +740,23 @@ pub fn process_create_address_with_seed(
|
658 | 740 | Ok(address.to_string())
|
659 | 741 | }
|
660 | 742 |
|
| 743 | +pub fn process_find_program_derived_address( |
| 744 | + config: &CliConfig, |
| 745 | + seeds: &Vec<Vec<u8>>, |
| 746 | + program_id: &Pubkey, |
| 747 | +) -> ProcessResult { |
| 748 | + if config.verbose { |
| 749 | + println!("Seeds: {seeds:?}"); |
| 750 | + } |
| 751 | + let seeds_slice = seeds.iter().map(|x| &x[..]).collect::<Vec<_>>(); |
| 752 | + let (address, bump_seed) = Pubkey::find_program_address(&seeds_slice[..], program_id); |
| 753 | + let result = CliFindProgramDerivedAddress { |
| 754 | + address: address.to_string(), |
| 755 | + bump_seed, |
| 756 | + }; |
| 757 | + Ok(config.output_format.formatted_string(&result)) |
| 758 | +} |
| 759 | + |
661 | 760 | #[allow(clippy::too_many_arguments)]
|
662 | 761 | pub fn process_transfer(
|
663 | 762 | rpc_client: &RpcClient,
|
|
0 commit comments