|
@@ -0,0 +1,493 @@
|
|
|
+use {
|
|
|
+ clap::{builder::BoolishValueParser, crate_description, crate_name, crate_version, Arg, Command}, solana_clap_v3_utils::{
|
|
|
+ input_parsers::{
|
|
|
+ parse_url_or_moniker,
|
|
|
+ signer::{SignerSource, SignerSourceParserBuilder},
|
|
|
+ },
|
|
|
+ input_validators::normalize_to_url_if_moniker,
|
|
|
+ keypair::signer_from_path,
|
|
|
+ }, solana_client::nonblocking::rpc_client::RpcClient, solana_remote_wallet::remote_wallet::RemoteWalletManager, solana_sdk::{
|
|
|
+ commitment_config::CommitmentConfig,
|
|
|
+ message::Message,
|
|
|
+ pubkey::Pubkey,
|
|
|
+ signature::{Signature, Signer},
|
|
|
+ transaction::Transaction,
|
|
|
+ }, spl_tlv_account_resolution::{account::ExtraAccountMeta, seeds::Seed, state::ExtraAccountMetaList}, spl_transfer_hook_interface::instruction::ExecuteInstruction, std::{error::Error, process::exit, rc::Rc, sync::Arc}
|
|
|
+};
|
|
|
+
|
|
|
+struct Config {
|
|
|
+ commitment_config: CommitmentConfig,
|
|
|
+ payer: Arc<dyn Signer>,
|
|
|
+ json_rpc_url: String,
|
|
|
+ verbose: bool,
|
|
|
+}
|
|
|
+
|
|
|
+pub fn get_extra_account_metas_with_source_wallet_block() -> Vec<ExtraAccountMeta> {
|
|
|
+ vec![
|
|
|
+ // [5] wallet_block for source token account wallet
|
|
|
+ ExtraAccountMeta::new_with_seeds(
|
|
|
+ &[
|
|
|
+ Seed::Literal {
|
|
|
+ bytes: b"wallet_block".to_vec(),
|
|
|
+ },
|
|
|
+ Seed::AccountData {
|
|
|
+ account_index: 0,
|
|
|
+ data_index: 32,
|
|
|
+ length: 32,
|
|
|
+ },
|
|
|
+ ],
|
|
|
+ false,
|
|
|
+ false,
|
|
|
+ ).unwrap(),
|
|
|
+ ]
|
|
|
+}
|
|
|
+pub fn get_extra_account_metas_with_both_wallet_blocks() -> Vec<ExtraAccountMeta> {
|
|
|
+ vec![
|
|
|
+ // [5] wallet_block for source token account wallet
|
|
|
+ ExtraAccountMeta::new_with_seeds(
|
|
|
+ &[
|
|
|
+ Seed::Literal {
|
|
|
+ bytes: b"wallet_block".to_vec(),
|
|
|
+ },
|
|
|
+ Seed::AccountData {
|
|
|
+ account_index: 0,
|
|
|
+ data_index: 32,
|
|
|
+ length: 32,
|
|
|
+ },
|
|
|
+ ],
|
|
|
+ false,
|
|
|
+ false,
|
|
|
+ ).unwrap(),
|
|
|
+ // [6] wallet_block for destination token account wallet
|
|
|
+ ExtraAccountMeta::new_with_seeds(
|
|
|
+ &[
|
|
|
+ Seed::Literal {
|
|
|
+ bytes: b"wallet_block".to_vec(),
|
|
|
+ },
|
|
|
+ Seed::AccountData {
|
|
|
+ account_index: 2,
|
|
|
+ data_index: 32,
|
|
|
+ length: 32,
|
|
|
+ },
|
|
|
+ ],
|
|
|
+ false,
|
|
|
+ false,
|
|
|
+ ).unwrap(),
|
|
|
+ ]
|
|
|
+}
|
|
|
+
|
|
|
+
|
|
|
+fn create_empty_extra_metas() -> Vec<u8> {
|
|
|
+ let size = ExtraAccountMetaList::size_of(0).unwrap();
|
|
|
+ let metas: Vec<ExtraAccountMeta> = vec![];
|
|
|
+ let mut data = vec![0; size];
|
|
|
+ ExtraAccountMetaList::init::<ExecuteInstruction>(&mut data, &metas).unwrap();
|
|
|
+ data
|
|
|
+}
|
|
|
+
|
|
|
+fn create_extra_metas_with_source_wallet_block() -> Vec<u8> {
|
|
|
+ let metas: Vec<ExtraAccountMeta> = get_extra_account_metas_with_source_wallet_block();
|
|
|
+ let size = ExtraAccountMetaList::size_of(metas.len()).unwrap();
|
|
|
+ let mut data = vec![0; size];
|
|
|
+ ExtraAccountMetaList::init::<ExecuteInstruction>(&mut data, &metas).unwrap();
|
|
|
+ data
|
|
|
+}
|
|
|
+fn create_extra_metas_with_both_wallet_blocks() -> Vec<u8> {
|
|
|
+ let metas: Vec<ExtraAccountMeta> = get_extra_account_metas_with_both_wallet_blocks();
|
|
|
+ let size = ExtraAccountMetaList::size_of(metas.len()).unwrap();
|
|
|
+ let mut data = vec![0; size];
|
|
|
+ ExtraAccountMetaList::init::<ExecuteInstruction>(&mut data, &metas).unwrap();
|
|
|
+ data
|
|
|
+}
|
|
|
+
|
|
|
+fn get_extra_metas_account_data() {
|
|
|
+ let data_empty = create_empty_extra_metas();
|
|
|
+ let data_source_wallet_block = create_extra_metas_with_source_wallet_block();
|
|
|
+ let data_both_wallet_blocks = create_extra_metas_with_both_wallet_blocks();
|
|
|
+
|
|
|
+ println!("data empty: {:?}", data_empty);
|
|
|
+ println!("data source wallet block: {:?}", data_source_wallet_block);
|
|
|
+ println!("data both wallet blocks: {:?}", data_both_wallet_blocks);
|
|
|
+}
|
|
|
+
|
|
|
+async fn get_config(rpc_client: &Arc<RpcClient>) {
|
|
|
+ let config = block_list_client::accounts::Config::find_pda().0;
|
|
|
+ let data = rpc_client.get_account_data(&config).await.unwrap();
|
|
|
+ println!("config: {:?}", data);
|
|
|
+
|
|
|
+ let config = block_list_client::accounts::Config::from_bytes(&data).unwrap();
|
|
|
+ println!("config: {:?}", config);
|
|
|
+}
|
|
|
+
|
|
|
+async fn get_extra_metas(rpc_client: &Arc<RpcClient>, mint_address: &Pubkey) {
|
|
|
+ let extra_metas = block_list_client::accounts::ExtraMetas::find_pda(mint_address).0;
|
|
|
+ let data = rpc_client.get_account_data(&extra_metas).await.unwrap();
|
|
|
+ println!("extra_metas: {:?}", data);
|
|
|
+
|
|
|
+}
|
|
|
+
|
|
|
+async fn process_setup_extra_metas(
|
|
|
+ rpc_client: &Arc<RpcClient>,
|
|
|
+ payer: &Arc<dyn Signer>,
|
|
|
+ mint_address: &Pubkey,
|
|
|
+ check_both_wallets: bool,
|
|
|
+) -> Result<Signature, Box<dyn Error>> {
|
|
|
+ let ix = block_list_client::instructions::SetupExtraMetasBuilder::new()
|
|
|
+ .authority(payer.pubkey())
|
|
|
+ .config(block_list_client::accounts::Config::find_pda().0)
|
|
|
+ .mint(*mint_address)
|
|
|
+ .extra_metas(block_list_client::accounts::ExtraMetas::find_pda(mint_address).0)
|
|
|
+ .check_both_wallets(check_both_wallets)
|
|
|
+ .instruction();
|
|
|
+
|
|
|
+ let mut transaction = Transaction::new_unsigned(Message::new(&[ix], Some(&payer.pubkey())));
|
|
|
+
|
|
|
+ let blockhash = rpc_client
|
|
|
+ .get_latest_blockhash()
|
|
|
+ .await
|
|
|
+ .map_err(|err| format!("error: unable to get latest blockhash: {}", err))?;
|
|
|
+
|
|
|
+ transaction
|
|
|
+ .try_sign(&[payer], blockhash)
|
|
|
+ .map_err(|err| format!("error: failed to sign transaction: {}", err))?;
|
|
|
+
|
|
|
+ let signature = rpc_client
|
|
|
+ .send_and_confirm_transaction_with_spinner(&transaction)
|
|
|
+ .await
|
|
|
+ .map_err(|err| format!("error: send transaction: {}", err))?;
|
|
|
+
|
|
|
+ Ok(signature)
|
|
|
+}
|
|
|
+
|
|
|
+async fn process_init(
|
|
|
+ rpc_client: &Arc<RpcClient>,
|
|
|
+ payer: &Arc<dyn Signer>,
|
|
|
+) -> Result<Signature, Box<dyn Error>> {
|
|
|
+
|
|
|
+ let ix = block_list_client::instructions::InitBuilder::new()
|
|
|
+ .authority(payer.pubkey())
|
|
|
+ .config(block_list_client::accounts::Config::find_pda().0)
|
|
|
+ .instruction();
|
|
|
+
|
|
|
+ let mut transaction = Transaction::new_unsigned(Message::new(&[ix], Some(&payer.pubkey())));
|
|
|
+
|
|
|
+ let blockhash = rpc_client
|
|
|
+ .get_latest_blockhash()
|
|
|
+ .await
|
|
|
+ .map_err(|err| format!("error: unable to get latest blockhash: {}", err))?;
|
|
|
+
|
|
|
+ transaction
|
|
|
+ .try_sign(&[payer], blockhash)
|
|
|
+ .map_err(|err| format!("error: failed to sign transaction: {}", err))?;
|
|
|
+
|
|
|
+ let signature = rpc_client
|
|
|
+ .send_and_confirm_transaction_with_spinner(&transaction)
|
|
|
+ .await
|
|
|
+ .map_err(|err| format!("error: send transaction: {}", err))?;
|
|
|
+
|
|
|
+ Ok(signature)
|
|
|
+}
|
|
|
+
|
|
|
+async fn process_block_wallet(
|
|
|
+ rpc_client: &Arc<RpcClient>,
|
|
|
+ payer: &Arc<dyn Signer>,
|
|
|
+ wallet_address: &Pubkey,
|
|
|
+) -> Result<Signature, Box<dyn Error>> {
|
|
|
+
|
|
|
+ let ix = block_list_client::instructions::BlockWalletBuilder::new()
|
|
|
+ .authority(payer.pubkey())
|
|
|
+ .config(block_list_client::accounts::Config::find_pda().0)
|
|
|
+ .wallet(*wallet_address)
|
|
|
+ .wallet_block(block_list_client::accounts::WalletBlock::find_pda(wallet_address).0)
|
|
|
+ .instruction();
|
|
|
+
|
|
|
+ let mut transaction = Transaction::new_unsigned(Message::new(&[ix], Some(&payer.pubkey())));
|
|
|
+
|
|
|
+ let blockhash = rpc_client
|
|
|
+ .get_latest_blockhash()
|
|
|
+ .await
|
|
|
+ .map_err(|err| format!("error: unable to get latest blockhash: {}", err))?;
|
|
|
+
|
|
|
+ transaction
|
|
|
+ .try_sign(&[payer], blockhash)
|
|
|
+ .map_err(|err| format!("error: failed to sign transaction: {}", err))?;
|
|
|
+
|
|
|
+ let signature = rpc_client
|
|
|
+ .send_and_confirm_transaction_with_spinner(&transaction)
|
|
|
+ .await
|
|
|
+ .map_err(|err| format!("error: send transaction: {}", err))?;
|
|
|
+
|
|
|
+ Ok(signature)
|
|
|
+}
|
|
|
+
|
|
|
+async fn process_unblock_wallet(
|
|
|
+ rpc_client: &Arc<RpcClient>,
|
|
|
+ payer: &Arc<dyn Signer>,
|
|
|
+ wallet_address: &Pubkey,
|
|
|
+) -> Result<Signature, Box<dyn Error>> {
|
|
|
+ let ix = block_list_client::instructions::UnblockWalletBuilder::new()
|
|
|
+ .authority(payer.pubkey())
|
|
|
+ .config(block_list_client::accounts::Config::find_pda().0)
|
|
|
+ .wallet_block(block_list_client::accounts::WalletBlock::find_pda(wallet_address).0)
|
|
|
+ .instruction();
|
|
|
+
|
|
|
+ let mut transaction = Transaction::new_unsigned(Message::new(&[ix], Some(&payer.pubkey())));
|
|
|
+
|
|
|
+ let blockhash = rpc_client
|
|
|
+ .get_latest_blockhash()
|
|
|
+ .await
|
|
|
+ .map_err(|err| format!("error: unable to get latest blockhash: {}", err))?;
|
|
|
+
|
|
|
+ transaction
|
|
|
+ .try_sign(&[payer], blockhash)
|
|
|
+ .map_err(|err| format!("error: failed to sign transaction: {}", err))?;
|
|
|
+
|
|
|
+ let signature = rpc_client
|
|
|
+ .send_and_confirm_transaction_with_spinner(&transaction)
|
|
|
+ .await
|
|
|
+ .map_err(|err| format!("error: send transaction: {}", err))?;
|
|
|
+
|
|
|
+ Ok(signature)
|
|
|
+}
|
|
|
+
|
|
|
+
|
|
|
+#[tokio::main]
|
|
|
+async fn main() -> Result<(), Box<dyn Error>> {
|
|
|
+ let app_matches = Command::new(crate_name!())
|
|
|
+ .about(crate_description!())
|
|
|
+ .version(crate_version!())
|
|
|
+ .subcommand_required(true)
|
|
|
+ .arg_required_else_help(true)
|
|
|
+ .arg({
|
|
|
+ let arg = Arg::new("config_file")
|
|
|
+ .short('C')
|
|
|
+ .long("config")
|
|
|
+ .value_name("PATH")
|
|
|
+ .takes_value(true)
|
|
|
+ .global(true)
|
|
|
+ .help("Configuration file to use");
|
|
|
+ if let Some(ref config_file) = *solana_cli_config::CONFIG_FILE {
|
|
|
+ arg.default_value(config_file)
|
|
|
+ } else {
|
|
|
+ arg
|
|
|
+ }
|
|
|
+ })
|
|
|
+ .arg(
|
|
|
+ Arg::new("payer")
|
|
|
+ .long("payer")
|
|
|
+ .value_name("KEYPAIR")
|
|
|
+ .value_parser(SignerSourceParserBuilder::default().allow_all().build())
|
|
|
+ .takes_value(true)
|
|
|
+ .global(true)
|
|
|
+ .help("Filepath or URL to a keypair [default: client keypair]"),
|
|
|
+ )
|
|
|
+ .arg(
|
|
|
+ Arg::new("verbose")
|
|
|
+ .long("verbose")
|
|
|
+ .short('v')
|
|
|
+ .takes_value(false)
|
|
|
+ .global(true)
|
|
|
+ .help("Show additional information"),
|
|
|
+ )
|
|
|
+ .arg(
|
|
|
+ Arg::new("json_rpc_url")
|
|
|
+ .short('u')
|
|
|
+ .long("url")
|
|
|
+ .value_name("URL")
|
|
|
+ .takes_value(true)
|
|
|
+ .global(true)
|
|
|
+ .value_parser(parse_url_or_moniker)
|
|
|
+ .help("JSON RPC URL for the cluster [default: value from configuration file]"),
|
|
|
+ )
|
|
|
+ .subcommand(
|
|
|
+ Command::new("init").about("Initializes the blocklist")
|
|
|
+ )
|
|
|
+ .subcommand(
|
|
|
+ Command::new("block-wallet").about("Blocks a wallet")
|
|
|
+ .arg(
|
|
|
+ Arg::new("wallet_address")
|
|
|
+ .value_name("WALLET_ADDRESS")
|
|
|
+ .value_parser(SignerSourceParserBuilder::default().allow_pubkey().build())
|
|
|
+ .takes_value(true)
|
|
|
+ .index(1)
|
|
|
+ .help("Specify the wallet address to block"),
|
|
|
+ )
|
|
|
+ )
|
|
|
+ .subcommand(
|
|
|
+ Command::new("unblock-wallet").about("Unblocks a wallet")
|
|
|
+ .arg(
|
|
|
+ Arg::new("wallet_address")
|
|
|
+ .value_name("WALLET_ADDRESS")
|
|
|
+ .value_parser(SignerSourceParserBuilder::default().allow_pubkey().build())
|
|
|
+ .takes_value(true)
|
|
|
+ .index(1)
|
|
|
+ .help("Specify the wallet address to unblock"),
|
|
|
+ )
|
|
|
+ )
|
|
|
+ .subcommand(
|
|
|
+ Command::new("get-extra-metas-account-data").about("Gets the extra metas account data")
|
|
|
+ )
|
|
|
+ .subcommand(
|
|
|
+ Command::new("get-config").about("Gets the config account data")
|
|
|
+ )
|
|
|
+ .subcommand(
|
|
|
+ Command::new("get-extra-metas").about("Gets the extra metas account data")
|
|
|
+ .arg(
|
|
|
+ Arg::new("mint_address")
|
|
|
+ .value_name("MINT_ADDRESS")
|
|
|
+ .value_parser(SignerSourceParserBuilder::default().allow_pubkey().build())
|
|
|
+ .takes_value(true)
|
|
|
+ .index(1)
|
|
|
+ .help("Specify the mint address"),
|
|
|
+ )
|
|
|
+ )
|
|
|
+ .subcommand(
|
|
|
+ Command::new("setup-extra-metas").about("Setup the extra metas account")
|
|
|
+ .arg(
|
|
|
+ Arg::new("mint_address")
|
|
|
+ .value_name("MINT_ADDRESS")
|
|
|
+ .value_parser(SignerSourceParserBuilder::default().allow_pubkey().build())
|
|
|
+ .takes_value(true)
|
|
|
+ .index(1)
|
|
|
+ .help("Specify the mint address"),
|
|
|
+ )
|
|
|
+ .arg(
|
|
|
+ Arg::new("check-both-wallets")
|
|
|
+ .long("check-both-wallets")
|
|
|
+ .short('b')
|
|
|
+ .help("Specify if both wallets should be checked"),
|
|
|
+ )
|
|
|
+ )
|
|
|
+ .get_matches();
|
|
|
+
|
|
|
+ let (command, matches) = app_matches.subcommand().unwrap();
|
|
|
+ let mut wallet_manager: Option<Rc<RemoteWalletManager>> = None;
|
|
|
+
|
|
|
+ let config = {
|
|
|
+ let cli_config = if let Some(config_file) = matches.try_get_one::<String>("config_file")? {
|
|
|
+ solana_cli_config::Config::load(config_file).unwrap_or_default()
|
|
|
+ } else {
|
|
|
+ solana_cli_config::Config::default()
|
|
|
+ };
|
|
|
+
|
|
|
+ let payer = if let Ok(Some((signer, _))) =
|
|
|
+ SignerSource::try_get_signer(matches, "payer", &mut wallet_manager)
|
|
|
+ {
|
|
|
+ Box::new(signer)
|
|
|
+ } else {
|
|
|
+ signer_from_path(
|
|
|
+ matches,
|
|
|
+ &cli_config.keypair_path,
|
|
|
+ "payer",
|
|
|
+ &mut wallet_manager,
|
|
|
+ )?
|
|
|
+ };
|
|
|
+
|
|
|
+ let json_rpc_url = normalize_to_url_if_moniker(
|
|
|
+ matches
|
|
|
+ .get_one::<String>("json_rpc_url")
|
|
|
+ .unwrap_or(&cli_config.json_rpc_url),
|
|
|
+ );
|
|
|
+
|
|
|
+ Config {
|
|
|
+ commitment_config: CommitmentConfig::confirmed(),
|
|
|
+ payer: Arc::from(payer),
|
|
|
+ json_rpc_url,
|
|
|
+ verbose: matches.try_contains_id("verbose")?,
|
|
|
+ }
|
|
|
+ };
|
|
|
+ solana_logger::setup_with_default("solana=info");
|
|
|
+
|
|
|
+ if config.verbose {
|
|
|
+ println!("JSON RPC URL: {}", config.json_rpc_url);
|
|
|
+ }
|
|
|
+ let rpc_client = Arc::new(RpcClient::new_with_commitment(
|
|
|
+ config.json_rpc_url.clone(),
|
|
|
+ config.commitment_config,
|
|
|
+ ));
|
|
|
+
|
|
|
+ match (command, matches) {
|
|
|
+ ("init", _arg_matches) => {
|
|
|
+ let response = process_init(
|
|
|
+ &rpc_client,
|
|
|
+ &config.payer,
|
|
|
+ )
|
|
|
+ .await
|
|
|
+ .unwrap_or_else(|err| {
|
|
|
+ eprintln!("error: init: {}", err);
|
|
|
+ exit(1);
|
|
|
+ });
|
|
|
+ println!("{}", response);
|
|
|
+ }
|
|
|
+ ("block-wallet", arg_matches) => {
|
|
|
+ let wallet_address =
|
|
|
+ SignerSource::try_get_pubkey(arg_matches, "wallet_address", &mut wallet_manager)
|
|
|
+ .unwrap()
|
|
|
+ .unwrap();
|
|
|
+ let response = process_block_wallet(
|
|
|
+ &rpc_client,
|
|
|
+ &config.payer,
|
|
|
+ &wallet_address,
|
|
|
+ )
|
|
|
+ .await
|
|
|
+ .unwrap_or_else(|err| {
|
|
|
+ eprintln!("error: init: {}", err);
|
|
|
+ exit(1);
|
|
|
+ });
|
|
|
+ println!("{}", response);
|
|
|
+ }
|
|
|
+ ("unblock-wallet", arg_matches) => {
|
|
|
+ let wallet_address =
|
|
|
+ SignerSource::try_get_pubkey(arg_matches, "wallet_address", &mut wallet_manager)
|
|
|
+ .unwrap()
|
|
|
+ .unwrap();
|
|
|
+ let response = process_unblock_wallet(
|
|
|
+ &rpc_client,
|
|
|
+ &config.payer,
|
|
|
+ &wallet_address,
|
|
|
+ )
|
|
|
+ .await
|
|
|
+ .unwrap_or_else(|err| {
|
|
|
+ eprintln!("error: init: {}", err);
|
|
|
+ exit(1);
|
|
|
+ });
|
|
|
+ println!("{}", response);
|
|
|
+ }
|
|
|
+ ("get-extra-metas-account-data", _arg_matches) => {
|
|
|
+ get_extra_metas_account_data();
|
|
|
+ }
|
|
|
+ ("get-config", _arg_matches) => {
|
|
|
+ get_config(&rpc_client).await;
|
|
|
+ }
|
|
|
+ ("get-extra-metas", arg_matches) => {
|
|
|
+ let mint_address =
|
|
|
+ SignerSource::try_get_pubkey(arg_matches, "mint_address", &mut wallet_manager)
|
|
|
+ .unwrap()
|
|
|
+ .unwrap();
|
|
|
+ get_extra_metas(&rpc_client, &mint_address).await;
|
|
|
+ }
|
|
|
+ ("setup-extra-metas", arg_matches) => {
|
|
|
+ let mint_address =
|
|
|
+ SignerSource::try_get_pubkey(arg_matches, "mint_address", &mut wallet_manager)
|
|
|
+ .unwrap()
|
|
|
+ .unwrap();
|
|
|
+ let check_both_wallets = arg_matches.contains_id("check-both-wallets");
|
|
|
+ let response = process_setup_extra_metas(
|
|
|
+ &rpc_client,
|
|
|
+ &config.payer,
|
|
|
+ &mint_address,
|
|
|
+ check_both_wallets,
|
|
|
+ )
|
|
|
+ .await
|
|
|
+ .unwrap_or_else(|err| {
|
|
|
+ eprintln!("error: setup_extra_metas: {}", err);
|
|
|
+ exit(1);
|
|
|
+ });
|
|
|
+ println!("{}", response);
|
|
|
+ }
|
|
|
+ _ => unreachable!(),
|
|
|
+ };
|
|
|
+
|
|
|
+ Ok(())
|
|
|
+}
|
|
|
+
|