From 5c1746e7c8b9d4395034ac2dd997b962ddc5d2ed Mon Sep 17 00:00:00 2001 From: Nikita Belenkov Date: Wed, 17 Dec 2025 22:06:55 +0100 Subject: [PATCH 1/3] adding choosing of network to display --- Cargo.lock | 6 -- src/commands/show.rs | 184 +++++++++++++++++++++++++++---------------- 2 files changed, 114 insertions(+), 76 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index 625cddc..699a7c9 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -1188,11 +1188,7 @@ checksum = "37909eebbb50d72f9059c3b6d82c0463f2ff062c9e95845c43a6c9c0355411be" name = "feature-gate-multisig-tool" version = "0.1.0" dependencies = [ - "anyhow", - "base64 0.22.1", - "bincode", "borsh 1.5.7", - "bs58", "clap 4.5.47", "colored", "dialoguer 0.11.0", @@ -1200,7 +1196,6 @@ dependencies = [ "eyre", "indicatif", "inquire", - "rand 0.8.5", "serde", "serde_json", "solana-clap-v3-utils", @@ -1213,7 +1208,6 @@ dependencies = [ "solana-message", "solana-pubkey", "solana-rent", - "solana-signature", "solana-signer", "solana-system-interface", "solana-transaction", diff --git a/src/commands/show.rs b/src/commands/show.rs index f515c66..ca0cfad 100644 --- a/src/commands/show.rs +++ b/src/commands/show.rs @@ -1,9 +1,13 @@ use crate::constants::*; -use crate::squads::{get_vault_pda, get_transaction_pda, get_proposal_pda, Multisig, VaultTransaction, Proposal, ProposalStatus}; -use crate::provision::{get_account_data_with_retry, create_rpc_client}; +use crate::provision::{create_rpc_client, get_account_data_with_retry}; +use crate::squads::{ + get_proposal_pda, get_transaction_pda, get_vault_pda, Multisig, Proposal, ProposalStatus, + VaultTransaction, +}; use crate::utils::*; -use eyre::Result; use colored::*; +use eyre::Result; +use inquire::Select; use solana_client::rpc_client::RpcClient; use solana_pubkey::Pubkey; use std::str::FromStr; @@ -31,8 +35,8 @@ pub async fn show_command(config: &Config, address: Option) -> Result<() async fn show_multisig(config: &Config, address: &str) -> Result<()> { // Parse the multisig address - let multisig_pubkey = Pubkey::from_str(address) - .map_err(|_| eyre::eyre!("Invalid multisig address format"))?; + let multisig_pubkey = + Pubkey::from_str(address).map_err(|_| eyre::eyre!("Invalid multisig address format"))?; println!( "{}", @@ -51,34 +55,27 @@ async fn show_multisig(config: &Config, address: &str) -> Result<()> { vec![DEFAULT_DEVNET_URL.to_string()] }; - println!("Available networks to search:"); - for (i, network) in networks_to_try.iter().enumerate() { - println!(" {}: {}", i + 1, network); - } - println!(); + let rpc_url: String = + Select::new("Which network would you like to query?", networks_to_try).prompt()?; - for rpc_url in &networks_to_try { - println!("🌐 Trying network: {}", rpc_url.bright_white()); + println!("🌐 Trying network: {}", rpc_url.bright_white()); - let rpc_client = create_rpc_client(rpc_url); - match get_account_data_with_retry(&rpc_client, &multisig_pubkey) { - Ok(data) => { - println!("✅ Found account on: {}", rpc_url.bright_green()); - account_data = Some(data); - successful_rpc_url = Some(rpc_url.clone()); - break; - } - Err(e) => { - let error_str = e.to_string(); - if error_str.contains("AccountNotFound") - || error_str.contains("could not find account") - { - println!("❌ Account not found on: {}", rpc_url.bright_red()); - last_error = Some(format!("Account not found: {}. This address may not exist on any of the configured networks or may not be a multisig account.", multisig_pubkey)); - } else { - println!("❌ Error querying {}: {}", rpc_url.bright_red(), e); - last_error = Some(format!("Failed to query networks: {}", e)); - } + let rpc_client = create_rpc_client(&rpc_url); + match get_account_data_with_retry(&rpc_client, &multisig_pubkey) { + Ok(data) => { + println!("✅ Found account on: {}", rpc_url.bright_green()); + account_data = Some(data); + successful_rpc_url = Some(rpc_url.clone()); + } + Err(e) => { + let error_str = e.to_string(); + if error_str.contains("AccountNotFound") || error_str.contains("could not find account") + { + println!("❌ Account not found on: {}", rpc_url.bright_red()); + last_error = Some(format!("Account not found: {}. This address may not exist on any of the configured networks or may not be a multisig account.", multisig_pubkey)); + } else { + println!("❌ Error querying {}: {}", rpc_url.bright_red(), e); + last_error = Some(format!("Failed to query networks: {}", e)); } } } @@ -104,9 +101,7 @@ async fn show_multisig(config: &Config, address: &str) -> Result<()> { println!(); if account_data.len() < 8 { - return Err(eyre::eyre!( - "Account data too small to be a valid multisig" - )); + return Err(eyre::eyre!("Account data too small to be a valid multisig")); } println!("📊 Account data length: {} bytes", account_data.len()); @@ -167,10 +162,15 @@ fn display_multisig_details(multisig: &Multisig, address: &Pubkey) -> Result<()> MultisigInfo { property: "Threshold".to_string(), value: { - let voting_members_count = multisig.members.iter() + let voting_members_count = multisig + .members + .iter() .filter(|member| member.permissions.mask & 2 != 0) // Check Vote permission (bit 1) .count(); - format!("{} of {} voting members", multisig.threshold, voting_members_count) + format!( + "{} of {} voting members", + multisig.threshold, voting_members_count + ) }, }, MultisigInfo { @@ -308,21 +308,24 @@ async fn fetch_and_display_transactions_and_proposals( return Ok(()); } - println!( - "🔍 Fetching transaction and proposal data for indices 1 and 2..." - ); + println!("🔍 Fetching transaction and proposal data for indices 1 and 2..."); println!(); // Fetch data for indices 1 and 2 for tx_index in 1..=2u64 { if tx_index > multisig.transaction_index { - println!("Transaction index {} not yet created (current max: {})", tx_index, multisig.transaction_index); + println!( + "Transaction index {} not yet created (current max: {})", + tx_index, multisig.transaction_index + ); continue; } println!( "{}", - format!("📋 TRANSACTION INDEX {}", tx_index).bright_cyan().bold() + format!("📋 TRANSACTION INDEX {}", tx_index) + .bright_cyan() + .bold() ); println!("{}", "─".repeat(50).bright_cyan()); @@ -330,23 +333,37 @@ async fn fetch_and_display_transactions_and_proposals( let (transaction_pda, _) = get_transaction_pda(multisig_pubkey, tx_index, None); let (proposal_pda, _) = get_proposal_pda(multisig_pubkey, tx_index, None); - println!("🎯 Transaction PDA: {}", transaction_pda.to_string().bright_white()); - println!("🎯 Proposal PDA: {}", proposal_pda.to_string().bright_white()); + println!( + "🎯 Transaction PDA: {}", + transaction_pda.to_string().bright_white() + ); + println!( + "🎯 Proposal PDA: {}", + proposal_pda.to_string().bright_white() + ); println!(); // Fetch transaction account match fetch_and_display_transaction(rpc_client, &transaction_pda, tx_index).await { - Ok(_) => {}, + Ok(_) => {} Err(e) => { - println!("❌ Failed to fetch transaction {}: {}", tx_index, e.to_string().bright_red()); + println!( + "❌ Failed to fetch transaction {}: {}", + tx_index, + e.to_string().bright_red() + ); } } - // Fetch proposal account + // Fetch proposal account match fetch_and_display_proposal(rpc_client, &proposal_pda, tx_index).await { - Ok(_) => {}, + Ok(_) => {} Err(e) => { - println!("❌ Failed to fetch proposal {}: {}", tx_index, e.to_string().bright_red()); + println!( + "❌ Failed to fetch proposal {}: {}", + tx_index, + e.to_string().bright_red() + ); } } @@ -381,13 +398,14 @@ async fn fetch_and_display_transaction( } // Deserialize the VaultTransaction - let transaction: VaultTransaction = match borsh::BorshDeserialize::deserialize(&mut &account_data[8..]) { - Ok(tx) => tx, - Err(e) => { - println!(" ❌ Failed to deserialize transaction: {}", e); - return Ok(()); - } - }; + let transaction: VaultTransaction = + match borsh::BorshDeserialize::deserialize(&mut &account_data[8..]) { + Ok(tx) => tx, + Err(e) => { + println!(" ❌ Failed to deserialize transaction: {}", e); + return Ok(()); + } + }; // Display transaction details in a table #[derive(Tabled)] @@ -436,7 +454,7 @@ async fn fetch_and_display_transaction( // Display transaction message details println!(); println!("📋 Transaction Message Details:"); - + #[derive(Tabled)] struct MessageInfo { #[tabled(rename = "Property")] @@ -480,7 +498,7 @@ async fn fetch_and_display_transaction( if !transaction.message.instructions.is_empty() { println!(); println!("📋 Instructions Details:"); - + #[derive(Tabled)] struct InstructionDetails { #[tabled(rename = "Instruction #")] @@ -493,12 +511,18 @@ async fn fetch_and_display_transaction( data: String, } - let instruction_details: Vec = transaction.message.instructions.iter() + let instruction_details: Vec = transaction + .message + .instructions + .iter() .enumerate() .map(|(i, instruction)| { // Get the program ID from account_keys - let program_id = if (instruction.program_id_index as usize) < transaction.message.account_keys.len() { - transaction.message.account_keys[instruction.program_id_index as usize].to_string() + let program_id = if (instruction.program_id_index as usize) + < transaction.message.account_keys.len() + { + transaction.message.account_keys[instruction.program_id_index as usize] + .to_string() } else { format!("Invalid index ({})", instruction.program_id_index) }; @@ -507,11 +531,17 @@ async fn fetch_and_display_transaction( let accounts_info = if instruction.account_indexes.is_empty() { "None".to_string() } else { - instruction.account_indexes.iter() + instruction + .account_indexes + .iter() .map(|&account_idx| { if (account_idx as usize) < transaction.message.account_keys.len() { - format!("{}:{}", account_idx, - &transaction.message.account_keys[account_idx as usize].to_string()[..8]) + format!( + "{}:{}", + account_idx, + &transaction.message.account_keys[account_idx as usize] + .to_string()[..8] + ) } else { format!("{}:Invalid", account_idx) } @@ -525,13 +555,17 @@ async fn fetch_and_display_transaction( "Empty".to_string() } else if instruction.data.len() <= 32 { // Show full data for small instructions - instruction.data.iter() + instruction + .data + .iter() .map(|b| format!("{:02x}", b)) .collect::>() .join(" ") } else { // Show first 16 bytes + length for large instructions - let preview = instruction.data.iter() + let preview = instruction + .data + .iter() .take(16) .map(|b| format!("{:02x}", b)) .collect::>() @@ -556,7 +590,7 @@ async fn fetch_and_display_transaction( if !transaction.message.account_keys.is_empty() { println!(); println!("🔑 Account Keys Reference:"); - + #[derive(Tabled)] struct AccountKeyInfo { #[tabled(rename = "Index")] @@ -567,7 +601,10 @@ async fn fetch_and_display_transaction( role: String, } - let account_key_info: Vec = transaction.message.account_keys.iter() + let account_key_info: Vec = transaction + .message + .account_keys + .iter() .enumerate() .map(|(i, pubkey)| { let role = if i < transaction.message.num_signers as usize { @@ -576,7 +613,11 @@ async fn fetch_and_display_transaction( } else { "Read-only Signer" } - } else if i < (transaction.message.num_signers + transaction.message.num_writable_non_signers) as usize { + } else if i + < (transaction.message.num_signers + + transaction.message.num_writable_non_signers) + as usize + { "Writable Non-signer" } else { "Read-only Non-signer" @@ -691,7 +732,10 @@ async fn fetch_and_display_proposal( println!("{}", proposal_table); // Display voting details if there are votes - if !proposal.approved.is_empty() || !proposal.rejected.is_empty() || !proposal.cancelled.is_empty() { + if !proposal.approved.is_empty() + || !proposal.rejected.is_empty() + || !proposal.cancelled.is_empty() + { println!(); println!("🗳️ Voting Details:"); @@ -735,4 +779,4 @@ async fn fetch_and_display_proposal( println!(); Ok(()) -} \ No newline at end of file +} From 000608cc422e5e3d1d7f34118e0f0bd0c70aa257 Mon Sep 17 00:00:00 2001 From: Nikita Belenkov Date: Wed, 17 Dec 2025 22:23:41 +0100 Subject: [PATCH 2/3] removed unused functions --- src/main.rs | 38 ++++----- src/provision.rs | 180 ------------------------------------------- src/utils.rs | 195 ----------------------------------------------- 3 files changed, 19 insertions(+), 394 deletions(-) diff --git a/src/main.rs b/src/main.rs index aca37d3..9f34b9a 100644 --- a/src/main.rs +++ b/src/main.rs @@ -8,10 +8,10 @@ mod utils; use crate::commands::{config_command, create_command, interactive_mode, show_command}; use crate::output::Output; -use crate::utils::{load_config, prompt_for_threshold}; -use eyre::Result; +use crate::utils::load_config; use clap::{Parser, Subcommand}; use colored::*; +use eyre::Result; #[derive(Parser)] #[command(name = "feature-gate-multisig-tool")] @@ -93,7 +93,6 @@ The contributor key receives Initiate-only permissions, while additional members Config, } - #[tokio::main] async fn main() { let cli = Cli::parse(); @@ -113,7 +112,9 @@ async fn main() { } else if error_msg.contains("address") || error_msg.contains("pubkey") { Output::hint("Public keys should be valid base58-encoded addresses"); } else if error_msg.contains("network") || error_msg.contains("URL") { - Output::hint("Network URLs should start with https:// (e.g., https://api.devnet.solana.com)"); + Output::hint( + "Network URLs should start with https:// (e.g., https://api.devnet.solana.com)", + ); } else if error_msg.contains("threshold") { Output::hint("Threshold must be a positive number not exceeding member count"); } @@ -133,25 +134,24 @@ async fn handle_command(command: Commands) -> Result<()> { signers: _, keypair, } => { - let threshold_option = threshold.map(|t| { - if t == 0 { - println!( - "{} Threshold cannot be 0, will prompt later", - "⚠️".bright_yellow() - ); - None - } else { - Some(t as u16) - } - }).flatten(); + let threshold_option = threshold + .map(|t| { + if t == 0 { + println!( + "{} Threshold cannot be 0, will prompt later", + "⚠️".bright_yellow() + ); + None + } else { + Some(t as u16) + } + }) + .flatten(); create_command(&mut config, threshold_option, vec![], keypair).await } - Commands::Show { address } => { - show_command(&config, address).await - } + Commands::Show { address } => show_command(&config, address).await, Commands::Interactive => interactive_mode().await, Commands::Config => config_command(&config).await, } } - diff --git a/src/provision.rs b/src/provision.rs index 44d311f..ca954d7 100644 --- a/src/provision.rs +++ b/src/provision.rs @@ -447,186 +447,6 @@ pub async fn create_multisig( Ok((multisig_key.0, signature)) } -pub async fn create_feature_gate_proposal( - rpc_urls: Vec, - program_id: Option, - multisig_pubkey: Pubkey, - contributor_keypair: &dyn Signer, - priority_fee_lamports: Option, -) -> eyre::Result<()> { - let program_id = program_id.unwrap_or_else(|| SQUADS_PROGRAM_ID_STR.to_string()); - let program_id = Pubkey::from_str(&program_id).expect("Invalid program ID"); - - let transaction_creator = contributor_keypair.pubkey(); - let vault_pda = get_vault_pda(&multisig_pubkey, 0, Some(&program_id)); - let feature_gate_id = vault_pda.0; // Use vault as feature gate ID - - println!(); - println!( - "{}", - "🚀 Creating feature gate proposals for multisig" - .bright_yellow() - .bold() - ); - println!(); - println!( - "{}: {}", - "Multisig".cyan(), - multisig_pubkey.to_string().bright_white() - ); - println!( - "{}: {}", - "Feature Gate ID".cyan(), - feature_gate_id.to_string().bright_white() - ); - println!( - "{}: {}", - "Networks".cyan(), - rpc_urls.len().to_string().bright_green() - ); - println!(); - - let proceed = Confirm::new() - .with_prompt("Do you want to proceed with creating feature gate proposals?") - .default(false) - .interact()?; - if !proceed { - println!("{}", "OK, aborting.".bright_red()); - return Err(eyre!("User aborted")); - } - println!(); - - for (network_idx, rpc_url) in rpc_urls.iter().enumerate() { - println!( - "Processing network {} ({}/{})", - rpc_url.bright_cyan(), - network_idx + 1, - rpc_urls.len() - ); - - let rpc_client = create_rpc_client(rpc_url); - let progress = - ProgressBar::new_spinner().with_message("Processing feature gate transactions..."); - progress.enable_steady_tick(Duration::from_millis(100)); - - let blockhash = rpc_client - .get_latest_blockhash() - .expect("Failed to get blockhash"); - - // Fetch current multisig state to get next transaction index - let multisig_account = rpc_client - .get_account(&multisig_pubkey) - .expect("Failed to fetch multisig account"); - - let multisig_data = multisig_account.data.as_slice(); - let multisig_data_without_discriminator = &multisig_data[8..]; - let multisig: crate::squads::Multisig = - borsh::from_slice(multisig_data_without_discriminator) - .expect("Failed to deserialize multisig"); - - let base_tx_index = multisig.transaction_index; - - // Create activation transaction and proposal (tx index 1) - let activation_tx_index = base_tx_index + 1; - - // Create revocation transaction and proposal (tx index 2) - let revocation_tx_index = base_tx_index + 2; - - // Create transaction messages using utility functions - let activation_message = - crate::utils::create_feature_activation_transaction_message(vault_pda.0); - let revocation_message = - crate::utils::create_feature_revocation_transaction_message(vault_pda.0); - - // Transaction 1: Create activation transaction and proposal in one step - let (activation_combined_message, activation_transaction_pda, activation_proposal_pda) = - create_transaction_and_proposal_message( - Some(&program_id), - &transaction_creator, - &transaction_creator, - &multisig_pubkey, - activation_tx_index, - 0, // vault_index - activation_message, - priority_fee_lamports.map(|fee| fee as u32), - Some(DEFAULT_COMPUTE_UNITS), // compute_unit_limit - blockhash, - )?; - - let activation_combined_transaction = VersionedTransaction::try_new( - VersionedMessage::V0(activation_combined_message), - &[contributor_keypair], - ) - .expect("Failed to create activation combined transaction"); - - let activation_combined_signature = - send_and_confirm_transaction(&activation_combined_transaction, &rpc_client)?; - - // Transaction 2: Create revocation transaction and proposal in one step - let (revocation_combined_message, revocation_transaction_pda, revocation_proposal_pda) = - create_transaction_and_proposal_message( - Some(&program_id), - &transaction_creator, - &transaction_creator, - &multisig_pubkey, - revocation_tx_index, - 0, // vault_index - revocation_message, - priority_fee_lamports.map(|fee| fee as u32), - Some(DEFAULT_COMPUTE_UNITS), // compute_unit_limit - blockhash, - )?; - - let revocation_combined_transaction = VersionedTransaction::try_new( - VersionedMessage::V0(revocation_combined_message), - &[contributor_keypair], - ) - .expect("Failed to create revocation combined transaction"); - - let revocation_combined_signature = - send_and_confirm_transaction(&revocation_combined_transaction, &rpc_client)?; - - progress.finish_with_message("Network completed!"); - - println!("✅ Network {} completed:", rpc_url.bright_cyan()); - println!( - " Activation Transaction & Proposal ({}): {}", - activation_tx_index, - activation_combined_signature.bright_cyan() - ); - println!( - " Transaction PDA: {}", - activation_transaction_pda.to_string().bright_white() - ); - println!( - " Proposal PDA: {}", - activation_proposal_pda.to_string().bright_white() - ); - println!( - " Revocation Transaction & Proposal ({}): {}", - revocation_tx_index, - revocation_combined_signature.bright_cyan() - ); - println!( - " Transaction PDA: {}", - revocation_transaction_pda.to_string().bright_white() - ); - println!( - " Proposal PDA: {}", - revocation_proposal_pda.to_string().bright_white() - ); - println!(); - } - - println!( - "{}", - "🎉 All feature gate proposals created successfully!" - .bright_green() - .bold() - ); - Ok(()) -} - pub fn create_transaction_and_proposal_message( program_id: Option<&Pubkey>, fee_payer_pubkey: &Pubkey, diff --git a/src/utils.rs b/src/utils.rs index 79b5ac9..6188493 100644 --- a/src/utils.rs +++ b/src/utils.rs @@ -115,14 +115,6 @@ pub fn parse_saved_members(config: &Config) -> Vec { parsed_members } -pub fn parse_saved_threshold(config: &Config) -> Option { - if config.threshold > 0 { - Some(config.threshold) - } else { - None - } -} - pub fn collect_members_interactively() -> Result> { let mut interactive_members = Vec::new(); @@ -212,49 +204,6 @@ pub fn expand_tilde_path(path: &str) -> Result { } } -pub fn load_fee_payer_keypair( - config: &Config, - keypair_path: Option, -) -> Result> { - if let Some(path) = keypair_path { - let keypair = Keypair::read_from_file(&path) - .map_err(|e| eyre::eyre!("Failed to load keypair from {}: {}", path, e))?; - Ok(Some(keypair)) - } else if let Some(path) = &config.fee_payer_path { - println!( - "{} Loading fee payer keypair from config: {}", - "💰".bright_blue(), - path.bright_white() - ); - let keypair = Keypair::read_from_file(path) - .map_err(|e| eyre::eyre!("Failed to load keypair from config path {}: {}", path, e))?; - Ok(Some(keypair)) - } else { - println!("{} No fee payer keypair provided", "⚠️".bright_yellow()); - Ok(None) - } -} - -// CLI input helpers -pub fn prompt_for_threshold(config: &Config) -> Result { - loop { - let input = Text::new(&format!( - "Enter threshold (required signatures) [{}]:", - config.threshold - )) - .prompt() - .unwrap_or_default(); - - match validate_threshold(&input, 10, config.threshold) { - Ok(t) => return Ok(t), - Err(e) => { - println!(" {} {}", "❌".bright_red(), e.to_string().bright_red()); - continue; - } - } - } -} - pub fn prompt_for_threshold_with_max(max_members: usize) -> Result { loop { let input = Text::new(&format!( @@ -319,71 +268,6 @@ pub fn prompt_for_network(config: &Config) -> Result { } } -// Display functions -pub fn display_final_configuration( - contributor_pubkey: &Pubkey, - create_key: &Pubkey, - fee_payer_keypair: &Option, - threshold: u16, - members: &[Member], -) { - println!("\n{}", "📋 Final Configuration:".bright_yellow().bold()); - println!( - " {}: {}", - "Contributor public key".cyan(), - contributor_pubkey.to_string().bright_white() - ); - println!( - " {}: {}", - "Create key".cyan(), - create_key.to_string().bright_white() - ); - if let Some(fee_payer) = fee_payer_keypair { - println!( - " {}: {}", - "Fee payer".cyan(), - fee_payer.pubkey().to_string().bright_green() - ); - } else { - println!( - " {}: {} (same as contributor)", - "Fee payer".cyan(), - contributor_pubkey.to_string().bright_yellow() - ); - } - println!( - " {}: {}", - "Threshold".cyan(), - threshold.to_string().bright_green() - ); - - println!("\n{}", "👥 All Members:".bright_yellow().bold()); - println!( - " {} Contributor: {} ({})", - "✓".bright_green(), - contributor_pubkey.to_string().bright_white(), - "Initiate".bright_cyan() - ); - - for (i, member) in members.iter().skip(1).enumerate() { - let perms = decode_permissions(member.permissions.mask); - println!( - " {} Member {}: {} ({})", - "✓".bright_green(), - i + 1, - member.key.to_string().bright_white(), - perms.join(", ").bright_cyan() - ); - } - - println!( - "\n{} {}", - "📊 Total members:".bright_yellow().bold(), - members.len().to_string().bright_green() - ); - println!(); -} - /// Build a TransactionMessage for a parent multisig to approve a child's proposal. /// This creates a single instruction that calls Squads `ApproveProposal` on the child multisig, /// with `member_pubkey` set to the parent multisig address. The resulting TransactionMessage can @@ -474,77 +358,6 @@ pub fn create_child_vote_approve_transaction_message( } } -pub fn display_deployment_info( - network_index: usize, - total_networks: usize, - rpc_url: &str, - create_key: &Pubkey, - contributor_pubkey: &Pubkey, - multisig_address: &Pubkey, - vault_address: &Pubkey, - members: &[Member], -) { - if total_networks > 1 { - println!( - "\n{} Deployment {} of {} to: {}", - "📡".bright_blue(), - (network_index + 1).to_string().bright_green(), - total_networks.to_string().bright_green(), - rpc_url.bright_white() - ); - } else { - println!( - "\n{} {}", - "🌐 Deploying to:".bright_blue().bold(), - rpc_url.bright_white() - ); - } - - println!( - "{}", - "📦 All public keys for this deployment:" - .bright_yellow() - .bold() - ); - - println!( - " {}: {}", - "Create key".cyan(), - create_key.to_string().bright_white() - ); - println!( - " {}: {}", - "Contributor".cyan(), - contributor_pubkey.to_string().bright_white() - ); - println!( - " {}: {}", - "Multisig PDA".cyan(), - multisig_address.to_string().bright_green() - ); - println!( - " {}: {}", - "Vault PDA (index 0)".cyan(), - vault_address.to_string().bright_green() - ); - - for (i, member) in members.iter().enumerate() { - let perms = decode_permissions(member.permissions.mask); - let label = if member.key == *contributor_pubkey { - "Contributor".to_string() - } else { - format!("Member {}", i) - }; - println!( - " {}: {} ({})", - label.cyan(), - member.key.to_string().bright_white(), - perms.join(", ").bright_cyan() - ); - } - println!(); -} - // Transaction creation functions pub fn create_feature_activation_transaction_message(feature_id: Pubkey) -> TransactionMessage { use crate::squads::SmallVec; @@ -924,14 +737,6 @@ impl Display for TransactionEncoding { ) } } -pub fn choose_transaction_encoding() -> Result { - let choice = Select::new( - "Transaction encoding format?", - vec![TransactionEncoding::Base58, TransactionEncoding::Base64], - ) - .prompt()?; - Ok(choice) -} pub fn choose_network_from_config(config: &Config) -> Result { let available_networks = if !config.networks.is_empty() { From e51acd934e0cfad74333104cb38fb5964899327d Mon Sep 17 00:00:00 2001 From: Nikita Belenkov Date: Wed, 17 Dec 2025 22:41:39 +0100 Subject: [PATCH 3/3] correcting readme index --- README.md | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/README.md b/README.md index b0b696c..299d661 100644 --- a/README.md +++ b/README.md @@ -7,8 +7,8 @@ This CLI tool enables the creation of feature gate multisigs across all Solana n The configuration file dictates the members and parent multisigs for the feature gate multisig to be created. The fee payer keypair is used to pay transaction fees and sets up the multisig configurations and proposals for a given feature gate. **Proposals are always created in the following order:** -1. **Feature Activation Proposal** (Index 0) -2. **Feature Activation Revocation Proposal** (Index 1) +1. **Feature Activation Proposal** (Index 1) +2. **Feature Activation Revocation Proposal** (Index 2) Once a feature gate multisig has been created, the CLI exposes transaction generation functionality to enable voting on either proposal and executing them when the threshold is met.