Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
48 changes: 31 additions & 17 deletions src/commands/deploy.rs
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
use crate::utils::{config, horizon, optimizer, print as p, soroban};
use crate::utils::{config, confirmation, horizon, optimizer, print as p, soroban};
use anyhow::Result;
use clap::Args;
use colored::*;
Expand Down Expand Up @@ -183,23 +183,37 @@ pub fn handle(args: DeployArgs) -> Result<()> {
p::separator();
}

if args.network == "mainnet" {
p::warn("You are deploying to MAINNET. This costs real XLM.");
}
// Build operation summary for confirmation
let risk_level = if args.network == "mainnet" {
confirmation::RiskLevel::High
} else {
confirmation::RiskLevel::Medium
};

if !args.yes {
println!();
print!(" Proceed? [y/N] ");
use std::io::BufRead;
let line = std::io::stdin()
.lock()
.lines()
.next()
.unwrap_or(Ok(String::new()))?;
if !matches!(line.trim().to_lowercase().as_str(), "y" | "yes") {
p::info("Deployment cancelled.");
return Ok(());
}
let summary = confirmation::OperationSummary::new(
"Deploy Soroban Contract".to_string(),
args.network.clone(),
risk_level,
)
.add("WASM file", &wasm_path.display().to_string())
.add("WASM size", &format!("{:.1} KB", wasm_size_kb))
.add("WASM hash", &wasm_hash)
.add("Wallet", &wallet.name)
.add("Public Key", &wallet.public_key)
.add("Optimized", if args.optimize { "Yes" } else { "No" })
.add("Execute", if args.execute { "Yes" } else { "No (dry-run)" });

let confirm_config = confirmation::ConfirmationConfig {
risk_level,
network: args.network.clone(),
skip_confirm: args.yes,
dry_run: !args.execute,
prompt: Some("Proceed with deployment?".to_string()),
require_type_confirmation: args.network == "mainnet",
};

if !confirmation::confirm_operation(&summary, &confirm_config)? {
return Ok(());
}

println!();
Expand Down
47 changes: 46 additions & 1 deletion src/commands/invoke.rs
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
use crate::utils::{config, print as p, soroban};
use crate::utils::{config, confirmation, print as p, soroban};
use anyhow::Result;
use clap::Args;

Expand Down Expand Up @@ -31,6 +31,10 @@ pub struct InvokeArgs {
/// Simulate only (don't submit transaction)
#[arg(long)]
simulate: bool,

/// Skip confirmation prompt
#[arg(long, default_value = "false")]
yes: bool,
}

#[allow(dead_code)]
Expand Down Expand Up @@ -114,6 +118,47 @@ pub fn handle(args: InvokeArgs) -> Result<()> {
}
}

// Add confirmation before actual submission
if !args.simulate {
let risk_level = if *network == "mainnet" {
confirmation::RiskLevel::High
} else {
confirmation::RiskLevel::Medium
};

let mut summary = confirmation::OperationSummary::new(
"Invoke Contract Function".to_string(),
network.clone(),
risk_level,
)
.add("Contract ID", &args.contract_id)
.add("Function", &args.function)
.add("Wallet", &args.wallet)
.add("Estimated Fee", &format!("{} stroops", outcome.simulation.fee))
.add("Return Value", &outcome.simulation.return_value);

// Add arguments to summary if present
if !arg_list.is_empty() {
for (i, (arg, arg_type)) in arg_list.iter().zip(arg_type_list.iter()).enumerate() {
summary = summary.add(&format!("Arg [{}] {}", i, arg_type), arg);
}
}

let confirm_config = confirmation::ConfirmationConfig {
risk_level,
network: network.clone(),
skip_confirm: args.yes,
dry_run: false,
prompt: Some("Submit this transaction?".to_string()),
require_type_confirmation: *network == "mainnet",
};

if !confirmation::confirm_operation(&summary, &confirm_config)? {
p::info("Transaction submission cancelled.");
return Ok(());
}
}

if let Some(tx) = outcome.transaction {
p::step(2, 2, "Submitting to network...");
println!();
Expand Down
99 changes: 72 additions & 27 deletions src/commands/tx.rs
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@ use anyhow::Result;
use clap::{Args, Subcommand};
use colored::*;

use crate::utils::confirmation;
use crate::utils::horizon::FeeStats;
use crate::utils::{config, crypto, horizon, print as p, tx_batch}; // Import FeeStats

Expand Down Expand Up @@ -189,19 +190,48 @@ fn handle_batch(args: BatchArgs) -> Result<()> {
),
);

if !args.yes {
println!();
print!(" Proceed with batch transaction? [y/N] ");
use std::io::BufRead;
let line = std::io::stdin()
.lock()
.lines()
.next()
.unwrap_or(Ok(String::new()))?;
if !matches!(line.trim().to_lowercase().as_str(), "y" | "yes") {
p::info("Batch transaction cancelled.");
return Ok(());
}
// Build operation summary for confirmation
let risk_level = if args.network == "mainnet" {
confirmation::RiskLevel::High
} else {
confirmation::RiskLevel::Medium
};

let mut summary = confirmation::OperationSummary::new(
"Batch Stellar Transaction".to_string(),
args.network.clone(),
risk_level,
)
.add("From Wallet", &wallet.name)
.add("From Address", &wallet.public_key)
.add("Operations", &doc.operations.len().to_string())
.add("Batch File", &args.file.display().to_string())
.add("Estimated Fee", &format!("{:.7} XLM", tx_result.fee as f64 / 10_000_000.0));

// Add operation details to summary
for (i, op) in payment_ops.iter().enumerate() {
let asset_label = match (&op.asset_code, &op.asset_issuer) {
(None, None) => "XLM".to_string(),
(Some(code), Some(issuer)) => format!("{}:{}", code, issuer),
_ => "unknown".to_string(),
};
summary = summary.add(
&format!("Op {}", i + 1),
&format!("payment → {} {} {}", op.destination, op.amount, asset_label),
);
}

let confirm_config = confirmation::ConfirmationConfig {
risk_level,
network: args.network.clone(),
skip_confirm: args.yes,
dry_run: false,
prompt: Some("Proceed with batch transaction?".to_string()),
require_type_confirmation: args.network == "mainnet",
};

if !confirmation::confirm_operation(&summary, &confirm_config)? {
return Ok(());
}

println!();
Expand Down Expand Up @@ -380,20 +410,35 @@ fn handle_send(args: SendArgs) -> Result<()> {
&format!("{}...", &tx_result.transaction_xdr[..20]),
);

// Confirmation prompt
if !args.yes {
println!();
print!(" Proceed with payment? [y/N] ");
use std::io::BufRead;
let line = std::io::stdin()
.lock()
.lines()
.next()
.unwrap_or(Ok(String::new()))?;
if !matches!(line.trim().to_lowercase().as_str(), "y" | "yes") {
p::info("Payment cancelled.");
return Ok(());
}
// Build operation summary for confirmation
let risk_level = if args.network == "mainnet" {
confirmation::RiskLevel::High
} else {
confirmation::RiskLevel::Medium
};

let summary = confirmation::OperationSummary::new(
"Send Stellar Payment".to_string(),
args.network.clone(),
risk_level,
)
.add("From Wallet", &wallet.name)
.add("From Address", &wallet.public_key)
.add("To Address", &args.to)
.add("Amount", &format!("{} {}", args.amount, args.asset))
.add("Estimated Fee", &format!("{:.7} XLM", tx_result.fee as f64 / 10_000_000.0));

let confirm_config = confirmation::ConfirmationConfig {
risk_level,
network: args.network.clone(),
skip_confirm: args.yes,
dry_run: false,
prompt: Some("Proceed with payment?".to_string()),
require_type_confirmation: args.network == "mainnet",
};

if !confirmation::confirm_operation(&summary, &confirm_config)? {
return Ok(());
}

// Submit transaction
Expand Down
91 changes: 58 additions & 33 deletions src/commands/upgrade.rs
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
use crate::utils::{config, horizon, print as p};
use crate::utils::{config, confirmation, horizon, print as p};
use anyhow::Result;
use chrono::Utc;
use clap::{Args, Subcommand};
Expand Down Expand Up @@ -554,23 +554,36 @@ fn handle_execute(args: ExecuteArgs) -> Result<()> {
p::kv("Network", &proposal.network);
p::kv("Executor", &wallet.public_key);

if args.network == "mainnet" {
p::warn("You are upgrading on MAINNET. This is irreversible without a rollback proposal.");
}
// Build operation summary for confirmation
let risk_level = if args.network == "mainnet" {
confirmation::RiskLevel::High
} else {
confirmation::RiskLevel::Medium
};

if !args.yes {
println!();
print!(" Execute upgrade? [y/N] ");
use std::io::BufRead;
let line = std::io::stdin()
.lock()
.lines()
.next()
.unwrap_or(Ok(String::new()))?;
if !matches!(line.trim().to_lowercase().as_str(), "y" | "yes") {
p::info("Upgrade cancelled.");
return Ok(());
}
let summary = confirmation::OperationSummary::new(
"Execute Contract Upgrade".to_string(),
args.network.clone(),
risk_level,
)
.add("Proposal ID", &proposal.id)
.add("Contract ID", &proposal.contract_id)
.add("New WASM hash", &proposal.new_wasm_hash)
.add("Network", &proposal.network)
.add("Executor", &wallet.public_key)
.add("Approvals", &format!("{}/{}", proposal.approvals.len(), proposal.threshold));

let confirm_config = confirmation::ConfirmationConfig {
risk_level,
network: args.network.clone(),
skip_confirm: args.yes,
dry_run: false,
prompt: Some("Execute this upgrade?".to_string()),
require_type_confirmation: args.network == "mainnet",
};

if !confirmation::confirm_operation(&summary, &confirm_config)? {
return Ok(());
}

println!();
Expand Down Expand Up @@ -651,23 +664,35 @@ fn handle_rollback(args: RollbackArgs) -> Result<()> {
p::kv("Originally from", &target.proposal_id);
p::kv("Network", &args.network);

if args.network == "mainnet" {
p::warn("Rolling back on MAINNET. Ensure backward compatibility before proceeding.");
}
// Build operation summary for confirmation
let risk_level = if args.network == "mainnet" {
confirmation::RiskLevel::High
} else {
confirmation::RiskLevel::Medium
};

if !args.yes {
println!();
print!(" Proceed with rollback? [y/N] ");
use std::io::BufRead;
let line = std::io::stdin()
.lock()
.lines()
.next()
.unwrap_or(Ok(String::new()))?;
if !matches!(line.trim().to_lowercase().as_str(), "y" | "yes") {
p::info("Rollback cancelled.");
return Ok(());
}
let summary = confirmation::OperationSummary::new(
"Contract Rollback".to_string(),
args.network.clone(),
risk_level,
)
.add("Contract ID", &args.contract_id)
.add("Rollback to", &args.to_hash)
.add("Originally from", &target.proposal_id)
.add("Network", &args.network)
.add("Executor", &wallet.public_key);

let confirm_config = confirmation::ConfirmationConfig {
risk_level,
network: args.network.clone(),
skip_confirm: args.yes,
dry_run: false,
prompt: Some("Proceed with rollback?".to_string()),
require_type_confirmation: args.network == "mainnet",
};

if !confirmation::confirm_operation(&summary, &confirm_config)? {
return Ok(());
}

println!();
Expand Down
Loading