From ded2b52ddf0386f72334a4dc1071f14dce7087b3 Mon Sep 17 00:00:00 2001 From: sean-sqds Date: Tue, 14 Apr 2026 17:40:59 -0400 Subject: [PATCH 1/6] feat(displays): commands to display vault and config transactions --- cli/Cargo.toml | 1 + cli/src/command/config_transaction_create.rs | 1 + cli/src/command/config_transaction_execute.rs | 1 + cli/src/command/display_vault.rs | 1 + cli/src/command/initiate_program_upgrade.rs | 1 + cli/src/command/initiate_transfer.rs | 1 + cli/src/command/mod.rs | 6 + cli/src/command/multisig_create.rs | 1 + cli/src/command/program_config_init.rs | 1 + cli/src/command/proposal_vote.rs | 1 + cli/src/command/show_config_transaction.rs | 174 ++++++++++++++++++ cli/src/command/show_transaction.rs | 158 ++++++++++++++++ .../vault_transaction_accounts_close.rs | 1 + cli/src/command/vault_transaction_create.rs | 1 + cli/src/command/vault_transaction_execute.rs | 1 + cli/src/main.rs | 2 + 16 files changed, 352 insertions(+) create mode 100644 cli/src/command/show_config_transaction.rs create mode 100644 cli/src/command/show_transaction.rs diff --git a/cli/Cargo.toml b/cli/Cargo.toml index a864fc6e..a1c5c166 100644 --- a/cli/Cargo.toml +++ b/cli/Cargo.toml @@ -27,3 +27,4 @@ clap_v3 = { package = "clap", version = "3.0", optional = false } # The version solana-address-lookup-table-interface = "2.2" spl-token = "8" spl-associated-token-account = "7" +bs58 = "0.5" diff --git a/cli/src/command/config_transaction_create.rs b/cli/src/command/config_transaction_create.rs index c78581df..42396dc9 100644 --- a/cli/src/command/config_transaction_create.rs +++ b/cli/src/command/config_transaction_create.rs @@ -32,6 +32,7 @@ use squads_multisig::state::{ConfigAction, Period, Permission, Permissions}; use crate::utils::{create_signer_from_path, send_and_confirm_transaction}; +/// Create a new config transaction (add/remove member, change threshold, etc.) and activate its proposal. #[derive(Args)] pub struct ConfigTransactionCreate { /// RPC URL diff --git a/cli/src/command/config_transaction_execute.rs b/cli/src/command/config_transaction_execute.rs index c6f9c6c8..1ef5c633 100644 --- a/cli/src/command/config_transaction_execute.rs +++ b/cli/src/command/config_transaction_execute.rs @@ -20,6 +20,7 @@ use squads_multisig::squads_multisig_program::instruction::ConfigTransactionExec use crate::utils::{create_signer_from_path, send_and_confirm_transaction}; +/// Execute an approved config transaction on-chain. #[derive(Args)] pub struct ConfigTransactionExecute { /// RPC URL diff --git a/cli/src/command/display_vault.rs b/cli/src/command/display_vault.rs index 56dadf9f..e781a5d9 100644 --- a/cli/src/command/display_vault.rs +++ b/cli/src/command/display_vault.rs @@ -4,6 +4,7 @@ use std::str::FromStr; use clap::Args; +/// Derive and display the vault PDA address for a given multisig and vault index. #[derive(Args)] pub struct DisplayVault { /// Multisig Program ID diff --git a/cli/src/command/initiate_program_upgrade.rs b/cli/src/command/initiate_program_upgrade.rs index fd8ebb51..fa085b2e 100644 --- a/cli/src/command/initiate_program_upgrade.rs +++ b/cli/src/command/initiate_program_upgrade.rs @@ -32,6 +32,7 @@ use squads_multisig::state::Permission; use crate::utils::{create_signer_from_path, send_and_confirm_transaction}; +/// Create and activate a vault transaction that upgrades a BPF upgradeable program. #[derive(Args)] pub struct InitiateProgramUpgrade { /// RPC URL diff --git a/cli/src/command/initiate_transfer.rs b/cli/src/command/initiate_transfer.rs index 4930a95c..44eab756 100644 --- a/cli/src/command/initiate_transfer.rs +++ b/cli/src/command/initiate_transfer.rs @@ -38,6 +38,7 @@ use crate::command::transfer_common::{ }; use crate::utils::{create_signer_from_path, send_and_confirm_transaction}; +/// Create and activate a vault transaction that transfers SOL or SPL tokens from the vault. #[derive(Args)] pub struct InitiateTransfer { /// RPC URL diff --git a/cli/src/command/mod.rs b/cli/src/command/mod.rs index 93cd497e..beba00cd 100644 --- a/cli/src/command/mod.rs +++ b/cli/src/command/mod.rs @@ -11,6 +11,8 @@ use crate::command::proposal_vote::ProposalVote; use crate::command::claim_rent::ClaimRent; use crate::command::vault_transaction_accounts_close::VaultTransactionAccountsClose; use crate::command::vault_transaction_create::VaultTransactionCreate; +use crate::command::show_config_transaction::ShowConfigTransaction; +use crate::command::show_transaction::ShowTransaction; use crate::command::vault_transaction_execute::VaultTransactionExecute; use clap::Subcommand; @@ -27,6 +29,8 @@ pub mod multisig_create; pub mod program_config_init; pub mod proposal_vote; pub mod claim_rent; +pub mod show_config_transaction; +pub mod show_transaction; pub mod vault_transaction_accounts_close; pub mod vault_transaction_create; pub mod vault_transaction_execute; @@ -47,4 +51,6 @@ pub enum Command { InitiateProgramUpgrade(InitiateProgramUpgrade), DisplayVault(DisplayVault), DisplayProposals(DisplayProposals), + ShowTransaction(ShowTransaction), + ShowConfigTransaction(ShowConfigTransaction), } diff --git a/cli/src/command/multisig_create.rs b/cli/src/command/multisig_create.rs index 27e9d369..27a8b696 100644 --- a/cli/src/command/multisig_create.rs +++ b/cli/src/command/multisig_create.rs @@ -26,6 +26,7 @@ use squads_multisig::state::{Member, Permissions}; use crate::utils::{create_signer_from_path, send_and_confirm_transaction}; +/// Create a new Squads multisig with the specified members and threshold. #[derive(Args)] pub struct MultisigCreate { /// RPC URL diff --git a/cli/src/command/program_config_init.rs b/cli/src/command/program_config_init.rs index ac44c5e5..03d5a0b5 100644 --- a/cli/src/command/program_config_init.rs +++ b/cli/src/command/program_config_init.rs @@ -22,6 +22,7 @@ use squads_multisig::squads_multisig_program::ProgramConfigInitArgs; use crate::utils::{create_signer_from_path, send_and_confirm_transaction}; +/// Initialize the global program config account (one-time setup, authority only). #[derive(Args)] pub struct ProgramConfigInit { /// RPC URL diff --git a/cli/src/command/proposal_vote.rs b/cli/src/command/proposal_vote.rs index f8945f69..36060cc1 100644 --- a/cli/src/command/proposal_vote.rs +++ b/cli/src/command/proposal_vote.rs @@ -25,6 +25,7 @@ use squads_multisig::squads_multisig_program::ProposalVoteArgs; use crate::utils::{create_signer_from_path, send_and_confirm_transaction}; +/// Cast an approve or reject vote on an existing proposal. #[derive(Args)] pub struct ProposalVote { /// RPC URL diff --git a/cli/src/command/show_config_transaction.rs b/cli/src/command/show_config_transaction.rs new file mode 100644 index 00000000..1322deb8 --- /dev/null +++ b/cli/src/command/show_config_transaction.rs @@ -0,0 +1,174 @@ +use clap::Args; +use colored::Colorize; +use solana_sdk::pubkey::Pubkey; +use squads_multisig::anchor_lang::AccountDeserialize; +use squads_multisig::solana_rpc_client::nonblocking::rpc_client::RpcClient; +use squads_multisig::squads_multisig_program::state::ConfigTransaction; +use squads_multisig::state::{ConfigAction, Period, Permission, Permissions}; +use std::str::FromStr; + +/// Fetch a config transaction account and display its decoded actions (add/remove member, change threshold, etc.). +#[derive(Args)] +pub struct ShowConfigTransaction { + /// RPC URL (default: https://api.mainnet-beta.solana.com) + #[arg(long)] + rpc_url: Option, + + /// The ConfigTransaction account address to inspect + #[arg(long)] + transaction_address: String, +} + +fn format_permissions(permissions: Permissions) -> String { + let mut parts = Vec::new(); + if permissions.has(Permission::Initiate) { + parts.push("Proposer"); + } + if permissions.has(Permission::Vote) { + parts.push("Voter"); + } + if permissions.has(Permission::Execute) { + parts.push("Executor"); + } + if parts.is_empty() { + "None".to_string() + } else { + parts.join(", ") + } +} + +fn format_period(period: Period) -> &'static str { + match period { + Period::OneTime => "One-time", + Period::Day => "Daily", + Period::Week => "Weekly", + Period::Month => "Monthly", + } +} + +impl ShowConfigTransaction { + pub async fn execute(self) -> eyre::Result<()> { + let rpc_url = self + .rpc_url + .unwrap_or_else(|| "https://api.mainnet-beta.solana.com".to_string()); + let transaction_address = Pubkey::from_str(&self.transaction_address)?; + + let rpc_client = RpcClient::new(rpc_url); + + let account_data = rpc_client.get_account(&transaction_address).await?.data; + + let config_tx = + ConfigTransaction::try_deserialize(&mut account_data.as_slice())?; + + println!(); + println!("{}", "Config Transaction Details".bold()); + println!(" Address: {}", transaction_address); + println!(" Multisig: {}", config_tx.multisig); + println!(" Creator: {}", config_tx.creator); + println!(" Index: {}", config_tx.index); + println!(); + + println!( + "{}", + format!("Actions ({})", config_tx.actions.len()).bold() + ); + println!(); + + for (i, action) in config_tx.actions.iter().enumerate() { + match action { + ConfigAction::AddMember { new_member } => { + println!("{}", format!("Action {}: Add Member", i + 1).yellow().bold()); + println!(" Key: {}", new_member.key); + println!( + " Permissions: {}", + format_permissions(new_member.permissions) + ); + } + ConfigAction::RemoveMember { old_member } => { + println!( + "{}", + format!("Action {}: Remove Member", i + 1).yellow().bold() + ); + println!(" Key: {}", old_member); + } + ConfigAction::ChangeThreshold { new_threshold } => { + println!( + "{}", + format!("Action {}: Change Threshold", i + 1).yellow().bold() + ); + println!(" New Threshold: {}", new_threshold); + } + ConfigAction::SetTimeLock { new_time_lock } => { + println!( + "{}", + format!("Action {}: Set Time Lock", i + 1).yellow().bold() + ); + println!(" New Time Lock: {} seconds", new_time_lock); + } + ConfigAction::AddSpendingLimit { + create_key, + vault_index, + mint, + amount, + period, + members, + destinations, + } => { + println!( + "{}", + format!("Action {}: Add Spending Limit", i + 1).yellow().bold() + ); + println!(" Create Key: {}", create_key); + println!(" Vault Index: {}", vault_index); + println!(" Mint: {}", mint); + println!(" Amount: {}", amount); + println!(" Period: {}", format_period(*period)); + if members.is_empty() { + println!(" Members: (all)"); + } else { + println!(" Members:"); + for m in members { + println!(" {}", m); + } + } + if destinations.is_empty() { + println!(" Destinations: (any)"); + } else { + println!(" Destinations:"); + for d in destinations { + println!(" {}", d); + } + } + } + ConfigAction::RemoveSpendingLimit { spending_limit } => { + println!( + "{}", + format!("Action {}: Remove Spending Limit", i + 1).yellow().bold() + ); + println!(" Spending Limit: {}", spending_limit); + } + ConfigAction::SetRentCollector { new_rent_collector } => { + println!( + "{}", + format!("Action {}: Set Rent Collector", i + 1).yellow().bold() + ); + match new_rent_collector { + Some(key) => println!(" New Rent Collector: {}", key), + None => println!(" New Rent Collector: (disabled)"), + } + } + _ => { + println!( + "{}", + format!("Action {}: (unknown action type)", i + 1) + .yellow() + .bold() + ); + } + } + println!(); + } + + Ok(()) + } +} diff --git a/cli/src/command/show_transaction.rs b/cli/src/command/show_transaction.rs new file mode 100644 index 00000000..1ec5c0bf --- /dev/null +++ b/cli/src/command/show_transaction.rs @@ -0,0 +1,158 @@ +use clap::Args; +use colored::Colorize; +use solana_address_lookup_table_interface::state::AddressLookupTable; +use solana_sdk::pubkey::Pubkey; +use squads_multisig::anchor_lang::AccountDeserialize; +use squads_multisig::solana_rpc_client::nonblocking::rpc_client::RpcClient; +use bs58; +use squads_multisig::squads_multisig_program::state::VaultTransaction; +use squads_multisig::state::VaultTransactionMessage; +use std::str::FromStr; + +/// Fetch a vault transaction account and display its decoded instructions, accounts, and address lookup tables. +#[derive(Args)] +pub struct ShowTransaction { + /// RPC URL (default: https://api.mainnet-beta.solana.com) + #[arg(long)] + rpc_url: Option, + + /// The VaultTransaction account address to inspect + #[arg(long)] + transaction_address: String, +} + +struct AccountEntry { + pubkey: Pubkey, + is_writable: bool, + is_signer: bool, +} + +impl ShowTransaction { + pub async fn execute(self) -> eyre::Result<()> { + let rpc_url = self + .rpc_url + .unwrap_or_else(|| "https://api.mainnet-beta.solana.com".to_string()); + let transaction_address = Pubkey::from_str(&self.transaction_address)?; + + let rpc_client = RpcClient::new(rpc_url); + + let account_data = rpc_client.get_account(&transaction_address).await?.data; + + let vault_tx = + VaultTransaction::try_deserialize(&mut account_data.as_slice())?; + + println!(); + println!("{}", "Transaction Details".bold()); + println!(" Address: {}", transaction_address); + println!(" Multisig: {}", vault_tx.multisig); + println!(" Creator: {}", vault_tx.creator); + println!(" Index: {}", vault_tx.index); + println!(" Vault Index: {}", vault_tx.vault_index); + println!(); + + let message = &vault_tx.message; + + // Build the full resolved account list: static keys first, then ALT-derived keys. + let mut all_accounts: Vec = message + .account_keys + .iter() + .enumerate() + .map(|(i, key)| AccountEntry { + pubkey: *key, + is_writable: message.is_static_writable_index(i), + is_signer: message.is_signer_index(i), + }) + .collect(); + + // Fetch each ALT, resolve its addresses, and append them to all_accounts. + // Track the resolved entries for the summary printed at the end. + let mut alt_summaries: Vec<(Pubkey, Vec, Vec)> = Vec::new(); + + for lookup in &message.address_table_lookups { + let alt_data = rpc_client.get_account(&lookup.account_key).await?.data; + let alt = AddressLookupTable::deserialize(&alt_data)?; + + let mut writable_pubkeys = Vec::new(); + let mut readonly_pubkeys = Vec::new(); + + for &idx in &lookup.writable_indexes { + let pubkey = alt.addresses[idx as usize]; + writable_pubkeys.push(pubkey); + all_accounts.push(AccountEntry { + pubkey, + is_writable: true, + is_signer: false, + }); + } + + for &idx in &lookup.readonly_indexes { + let pubkey = alt.addresses[idx as usize]; + readonly_pubkeys.push(pubkey); + all_accounts.push(AccountEntry { + pubkey, + is_writable: false, + is_signer: false, + }); + } + + alt_summaries.push((lookup.account_key, writable_pubkeys, readonly_pubkeys)); + } + + // Print each instruction. + println!( + "{}", + format!("Instructions ({})", message.instructions.len()).bold() + ); + println!(); + + for (i, ix) in message.instructions.iter().enumerate() { + let program_pubkey = all_accounts[ix.program_id_index as usize].pubkey; + + println!("{}", format!("Instruction {}", i + 1).yellow().bold()); + println!(" Program: {}", program_pubkey); + + if !ix.account_indexes.is_empty() { + println!(" Accounts:"); + for (j, &idx) in ix.account_indexes.iter().enumerate() { + let acc = &all_accounts[idx as usize]; + let flags = match (acc.is_writable, acc.is_signer) { + (true, true) => " [writable, signer]".cyan().to_string(), + (true, false) => " [writable]".cyan().to_string(), + (false, true) => " [signer]".cyan().to_string(), + (false, false) => String::new(), + }; + println!(" {}: {}{}", j + 1, acc.pubkey, flags); + } + } + + if !ix.data.is_empty() { + println!(" Data: {}", bs58::encode(&ix.data).into_string()); + } + + println!(); + } + + // Print ALT summary if any were used. + if !alt_summaries.is_empty() { + println!("{}", "Address Lookup Tables".bold()); + for (key, writable, readonly) in &alt_summaries { + println!(" {}", key); + if !writable.is_empty() { + println!(" Writable:"); + for pk in writable { + println!(" {}", pk); + } + } + if !readonly.is_empty() { + println!(" Readonly:"); + for pk in readonly { + println!(" {}", pk); + } + } + } + println!(); + } + + Ok(()) + } +} diff --git a/cli/src/command/vault_transaction_accounts_close.rs b/cli/src/command/vault_transaction_accounts_close.rs index 316b748b..69725b35 100644 --- a/cli/src/command/vault_transaction_accounts_close.rs +++ b/cli/src/command/vault_transaction_accounts_close.rs @@ -22,6 +22,7 @@ use squads_multisig::squads_multisig_program::instruction::VaultTransactionAccou use crate::utils::{create_signer_from_path, send_and_confirm_transaction}; +/// Reclaim rent by closing the accounts of an executed, rejected, or cancelled vault transaction. #[derive(Args)] pub struct VaultTransactionAccountsClose { /// RPC URL diff --git a/cli/src/command/vault_transaction_create.rs b/cli/src/command/vault_transaction_create.rs index ffb8ddfd..40ec3b09 100644 --- a/cli/src/command/vault_transaction_create.rs +++ b/cli/src/command/vault_transaction_create.rs @@ -31,6 +31,7 @@ use squads_multisig::state::Permission; use crate::utils::{create_signer_from_path, send_and_confirm_transaction}; +/// Create a new vault transaction and activate its proposal for voting. #[derive(Args)] pub struct VaultTransactionCreate { /// RPC URL diff --git a/cli/src/command/vault_transaction_execute.rs b/cli/src/command/vault_transaction_execute.rs index 4f81066c..89225e0c 100644 --- a/cli/src/command/vault_transaction_execute.rs +++ b/cli/src/command/vault_transaction_execute.rs @@ -25,6 +25,7 @@ use std::time::Duration; use crate::utils::{create_signer_from_path, send_and_confirm_transaction}; +/// Execute an approved vault transaction on-chain. #[derive(Args)] pub struct VaultTransactionExecute { /// RPC URL diff --git a/cli/src/main.rs b/cli/src/main.rs index 53387b7a..a43d29b6 100644 --- a/cli/src/main.rs +++ b/cli/src/main.rs @@ -30,5 +30,7 @@ async fn main() -> eyre::Result<()> { Command::InitiateProgramUpgrade(command) => command.execute().await, Command::DisplayVault(command) => command.execute().await, Command::DisplayProposals(command) => command.execute().await, + Command::ShowTransaction(command) => command.execute().await, + Command::ShowConfigTransaction(command) => command.execute().await, } } From 47d24887d7c01e668664954efa8d406929dfbfdd Mon Sep 17 00:00:00 2001 From: sean-sqds Date: Tue, 14 Apr 2026 18:05:31 -0400 Subject: [PATCH 2/6] feat(display): change command names --- .gitignore | 4 +++- Cargo.lock | 1 + ..._transaction.rs => display_config_transaction.rs} | 4 ++-- .../{show_transaction.rs => display_transaction.rs} | 4 ++-- cli/src/command/mod.rs | 12 ++++++------ cli/src/main.rs | 4 ++-- 6 files changed, 16 insertions(+), 13 deletions(-) rename cli/src/command/{show_config_transaction.rs => display_config_transaction.rs} (98%) rename cli/src/command/{show_transaction.rs => display_transaction.rs} (98%) diff --git a/.gitignore b/.gitignore index d1d46bb8..00c2cb7b 100644 --- a/.gitignore +++ b/.gitignore @@ -16,4 +16,6 @@ lib .env.* .env -cli/*.sh \ No newline at end of file +cli/*.sh + +.idea \ No newline at end of file diff --git a/Cargo.lock b/Cargo.lock index 813fd1db..c8259b0e 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -5122,6 +5122,7 @@ name = "squads-multisig-cli" version = "0.1.7" dependencies = [ "bincode", + "bs58", "clap 3.2.25", "clap 4.5.53", "colored", diff --git a/cli/src/command/show_config_transaction.rs b/cli/src/command/display_config_transaction.rs similarity index 98% rename from cli/src/command/show_config_transaction.rs rename to cli/src/command/display_config_transaction.rs index 1322deb8..481f0770 100644 --- a/cli/src/command/show_config_transaction.rs +++ b/cli/src/command/display_config_transaction.rs @@ -9,7 +9,7 @@ use std::str::FromStr; /// Fetch a config transaction account and display its decoded actions (add/remove member, change threshold, etc.). #[derive(Args)] -pub struct ShowConfigTransaction { +pub struct DisplayConfigTransaction { /// RPC URL (default: https://api.mainnet-beta.solana.com) #[arg(long)] rpc_url: Option, @@ -46,7 +46,7 @@ fn format_period(period: Period) -> &'static str { } } -impl ShowConfigTransaction { +impl DisplayConfigTransaction { pub async fn execute(self) -> eyre::Result<()> { let rpc_url = self .rpc_url diff --git a/cli/src/command/show_transaction.rs b/cli/src/command/display_transaction.rs similarity index 98% rename from cli/src/command/show_transaction.rs rename to cli/src/command/display_transaction.rs index 1ec5c0bf..5ccedcf3 100644 --- a/cli/src/command/show_transaction.rs +++ b/cli/src/command/display_transaction.rs @@ -11,7 +11,7 @@ use std::str::FromStr; /// Fetch a vault transaction account and display its decoded instructions, accounts, and address lookup tables. #[derive(Args)] -pub struct ShowTransaction { +pub struct DisplayTransaction { /// RPC URL (default: https://api.mainnet-beta.solana.com) #[arg(long)] rpc_url: Option, @@ -27,7 +27,7 @@ struct AccountEntry { is_signer: bool, } -impl ShowTransaction { +impl DisplayTransaction { pub async fn execute(self) -> eyre::Result<()> { let rpc_url = self .rpc_url diff --git a/cli/src/command/mod.rs b/cli/src/command/mod.rs index beba00cd..338ee582 100644 --- a/cli/src/command/mod.rs +++ b/cli/src/command/mod.rs @@ -11,8 +11,8 @@ use crate::command::proposal_vote::ProposalVote; use crate::command::claim_rent::ClaimRent; use crate::command::vault_transaction_accounts_close::VaultTransactionAccountsClose; use crate::command::vault_transaction_create::VaultTransactionCreate; -use crate::command::show_config_transaction::ShowConfigTransaction; -use crate::command::show_transaction::ShowTransaction; +use crate::command::display_config_transaction::DisplayConfigTransaction; +use crate::command::display_transaction::DisplayTransaction; use crate::command::vault_transaction_execute::VaultTransactionExecute; use clap::Subcommand; @@ -29,8 +29,8 @@ pub mod multisig_create; pub mod program_config_init; pub mod proposal_vote; pub mod claim_rent; -pub mod show_config_transaction; -pub mod show_transaction; +pub mod display_config_transaction; +pub mod display_transaction; pub mod vault_transaction_accounts_close; pub mod vault_transaction_create; pub mod vault_transaction_execute; @@ -51,6 +51,6 @@ pub enum Command { InitiateProgramUpgrade(InitiateProgramUpgrade), DisplayVault(DisplayVault), DisplayProposals(DisplayProposals), - ShowTransaction(ShowTransaction), - ShowConfigTransaction(ShowConfigTransaction), + DisplayTransaction(DisplayTransaction), + DisplayConfigTransaction(DisplayConfigTransaction), } diff --git a/cli/src/main.rs b/cli/src/main.rs index a43d29b6..fec75b2e 100644 --- a/cli/src/main.rs +++ b/cli/src/main.rs @@ -30,7 +30,7 @@ async fn main() -> eyre::Result<()> { Command::InitiateProgramUpgrade(command) => command.execute().await, Command::DisplayVault(command) => command.execute().await, Command::DisplayProposals(command) => command.execute().await, - Command::ShowTransaction(command) => command.execute().await, - Command::ShowConfigTransaction(command) => command.execute().await, + Command::DisplayTransaction(command) => command.execute().await, + Command::DisplayConfigTransaction(command) => command.execute().await, } } From a33a5c2b963f6ba2e7bb947b5086dabba450945f Mon Sep 17 00:00:00 2001 From: sean-sqds Date: Tue, 14 Apr 2026 18:09:20 -0400 Subject: [PATCH 3/6] feat(docs): update readme for new commands --- cli/README.md | 48 ++++++++++++++++++++++++++++ cli/src/command/display_proposals.rs | 1 + 2 files changed, 49 insertions(+) diff --git a/cli/README.md b/cli/README.md index 4a5deea1..6e532067 100644 --- a/cli/README.md +++ b/cli/README.md @@ -14,6 +14,8 @@ Overview - [Reclaim Vault Transaction rent](#vault-transaction-accounts-close) - [Create new Vault Transaction](#vault-transaction-create) - [Execute Vault Transaction](#vault-transaction-execute) + - [Display Vault Transaction](#display-transaction) + - [Display Config Transaction](#display-config-transaction) # 1. Installation @@ -342,3 +344,49 @@ vault-transaction-execute --keypair /path/to/keypair.json --multisig-pubkey [--rpc-url ] +``` + +### Parameters + +- `--transaction-address `: The public key of the VaultTransaction account to inspect. +- `--rpc-url `: (Optional) The URL of the Solana RPC endpoint. Defaults to `https://api.mainnet-beta.solana.com`. + +### Example Usage + +```bash +squads-multisig-cli display-transaction --transaction-address 279SBhVyHLEBsphBkDBNMUbSoA31yRBDtnU5mzSM3d5n +``` + +## Display Config Transaction + +### Description + +Fetches a config transaction account and displays its decoded actions, such as adding or removing members, changing the threshold, setting the time lock, managing spending limits, and updating the rent collector. + +### Syntax + +```bash +display-config-transaction --transaction-address [--rpc-url ] +``` + +### Parameters + +- `--transaction-address `: The public key of the ConfigTransaction account to inspect. +- `--rpc-url `: (Optional) The URL of the Solana RPC endpoint. Defaults to `https://api.mainnet-beta.solana.com`. + +### Example Usage + +```bash +squads-multisig-cli display-config-transaction --transaction-address AQb6VyZGzC2kL7vFU7WqoTJYTNZdHKhKuHmzuxkqGGjV +``` diff --git a/cli/src/command/display_proposals.rs b/cli/src/command/display_proposals.rs index 8d500640..50308f3b 100644 --- a/cli/src/command/display_proposals.rs +++ b/cli/src/command/display_proposals.rs @@ -10,6 +10,7 @@ use squads_multisig::pda::get_proposal_pda; use squads_multisig::solana_rpc_client::nonblocking::rpc_client::RpcClient; use squads_multisig::state::{Multisig, Proposal, ProposalStatus}; +/// Fetch and display all proposals for a multisig, showing their status and transaction index. #[derive(Args)] pub struct DisplayProposals { /// RPC URL From caffaa66d6ce565a024c3d7772d021a18c2577c2 Mon Sep 17 00:00:00 2001 From: sean-sqds Date: Tue, 14 Apr 2026 18:14:10 -0400 Subject: [PATCH 4/6] feat(semver): version bump --- cli/Cargo.toml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/cli/Cargo.toml b/cli/Cargo.toml index a1c5c166..62ce8f8d 100644 --- a/cli/Cargo.toml +++ b/cli/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "squads-multisig-cli" -version = "0.1.7" +version = "0.1.8" edition = "2021" authors = ["Valentin Madrid", "Vova Guguiev"] license = "MIT OR Apache-2.0" From 2e3bf3b82aa9094ff02ba731c875d27adf2a0ec7 Mon Sep 17 00:00:00 2001 From: sean-sqds Date: Tue, 14 Apr 2026 18:19:18 -0400 Subject: [PATCH 5/6] feat(display): show missing account data message --- Cargo.lock | 2 +- cli/src/command/display_config_transaction.rs | 8 +++++++- cli/src/command/display_transaction.rs | 8 +++++++- 3 files changed, 15 insertions(+), 3 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index c8259b0e..30f9bab9 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -5119,7 +5119,7 @@ dependencies = [ [[package]] name = "squads-multisig-cli" -version = "0.1.7" +version = "0.1.8" dependencies = [ "bincode", "bs58", diff --git a/cli/src/command/display_config_transaction.rs b/cli/src/command/display_config_transaction.rs index 481f0770..deff345c 100644 --- a/cli/src/command/display_config_transaction.rs +++ b/cli/src/command/display_config_transaction.rs @@ -55,7 +55,13 @@ impl DisplayConfigTransaction { let rpc_client = RpcClient::new(rpc_url); - let account_data = rpc_client.get_account(&transaction_address).await?.data; + let account_data = match rpc_client.get_account(&transaction_address).await { + Ok(account) => account.data, + Err(_) => { + println!("Account closed or not found."); + return Ok(()); + } + }; let config_tx = ConfigTransaction::try_deserialize(&mut account_data.as_slice())?; diff --git a/cli/src/command/display_transaction.rs b/cli/src/command/display_transaction.rs index 5ccedcf3..95ffe93b 100644 --- a/cli/src/command/display_transaction.rs +++ b/cli/src/command/display_transaction.rs @@ -36,7 +36,13 @@ impl DisplayTransaction { let rpc_client = RpcClient::new(rpc_url); - let account_data = rpc_client.get_account(&transaction_address).await?.data; + let account_data = match rpc_client.get_account(&transaction_address).await { + Ok(account) => account.data, + Err(_) => { + println!("Account closed or not found."); + return Ok(()); + } + }; let vault_tx = VaultTransaction::try_deserialize(&mut account_data.as_slice())?; From a38ffaa1fcfbe00cc59facb955cb66d92082f165 Mon Sep 17 00:00:00 2001 From: sean-sqds Date: Wed, 15 Apr 2026 07:46:43 -0400 Subject: [PATCH 6/6] feat(display): use exported b58 --- Cargo.lock | 1 - cli/Cargo.toml | 3 +-- cli/src/command/display_transaction.rs | 2 +- 3 files changed, 2 insertions(+), 4 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index 30f9bab9..27031caf 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -5122,7 +5122,6 @@ name = "squads-multisig-cli" version = "0.1.8" dependencies = [ "bincode", - "bs58", "clap 3.2.25", "clap 4.5.53", "colored", diff --git a/cli/Cargo.toml b/cli/Cargo.toml index 62ce8f8d..63320486 100644 --- a/cli/Cargo.toml +++ b/cli/Cargo.toml @@ -26,5 +26,4 @@ solana-program = "2.2.20" clap_v3 = { package = "clap", version = "3.0", optional = false } # The version needed for solana_clap_utils solana-address-lookup-table-interface = "2.2" spl-token = "8" -spl-associated-token-account = "7" -bs58 = "0.5" +spl-associated-token-account = "7" \ No newline at end of file diff --git a/cli/src/command/display_transaction.rs b/cli/src/command/display_transaction.rs index 95ffe93b..a47bfdb5 100644 --- a/cli/src/command/display_transaction.rs +++ b/cli/src/command/display_transaction.rs @@ -4,7 +4,7 @@ use solana_address_lookup_table_interface::state::AddressLookupTable; use solana_sdk::pubkey::Pubkey; use squads_multisig::anchor_lang::AccountDeserialize; use squads_multisig::solana_rpc_client::nonblocking::rpc_client::RpcClient; -use bs58; +use solana_sdk::bs58; use squads_multisig::squads_multisig_program::state::VaultTransaction; use squads_multisig::state::VaultTransactionMessage; use std::str::FromStr;