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, json_rpc_url: String, verbose: bool, } pub fn get_extra_account_metas_with_source_wallet_block() -> Vec { 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 { 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 { let size = ExtraAccountMetaList::size_of(0).unwrap(); let metas: Vec = vec![]; let mut data = vec![0; size]; ExtraAccountMetaList::init::(&mut data, &metas).unwrap(); data } fn create_extra_metas_with_source_wallet_block() -> Vec { let metas: Vec = get_extra_account_metas_with_source_wallet_block(); let size = ExtraAccountMetaList::size_of(metas.len()).unwrap(); let mut data = vec![0; size]; ExtraAccountMetaList::init::(&mut data, &metas).unwrap(); data } fn create_extra_metas_with_both_wallet_blocks() -> Vec { let metas: Vec = get_extra_account_metas_with_both_wallet_blocks(); let size = ExtraAccountMetaList::size_of(metas.len()).unwrap(); let mut data = vec![0; size]; ExtraAccountMetaList::init::(&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) { 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, 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, payer: &Arc, mint_address: &Pubkey, check_both_wallets: bool, ) -> Result> { 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, payer: &Arc, ) -> Result> { 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, payer: &Arc, wallet_address: &Pubkey, ) -> Result> { 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, payer: &Arc, wallet_address: &Pubkey, ) -> Result> { 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> { 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> = None; let config = { let cli_config = if let Some(config_file) = matches.try_get_one::("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::("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(()) }