From 213f98b2e7a522f1396a34d660d3e3ac77703331 Mon Sep 17 00:00:00 2001 From: oyindamola oladapo Date: Mon, 15 Sep 2025 12:09:27 +0100 Subject: [PATCH] fix: improve error handling when no spendable UTXOs Previously, attempting payjoin operations with an empty wallet resulted in a generic "500 Internal Server Error" from bitcoind. This created a poor user experience because users could not understand why their payjoin failed or what action to take. --- payjoin-cli/src/app/mod.rs | 7 +++++++ payjoin-cli/src/app/v1.rs | 9 +++++++++ payjoin-cli/src/app/v2/mod.rs | 6 ++++++ payjoin-cli/src/app/wallet.rs | 6 ++++++ 4 files changed, 28 insertions(+) diff --git a/payjoin-cli/src/app/mod.rs b/payjoin-cli/src/app/mod.rs index 2c7898d8b..a54509c95 100644 --- a/payjoin-cli/src/app/mod.rs +++ b/payjoin-cli/src/app/mod.rs @@ -34,6 +34,13 @@ pub trait App: Send + Sync { amount: Amount, fee_rate: FeeRate, ) -> Result { + // Check if wallet has spendable UTXOs before attempting to create PSBT + if !self.wallet().has_spendable_utxos()? { + return Err(anyhow::anyhow!( + "No spendable UTXOs available in wallet. Please ensure your wallet has confirmed funds." + )); + } + // wallet_create_funded_psbt requires a HashMap let mut outputs = HashMap::with_capacity(1); outputs.insert(address.to_string(), amount); diff --git a/payjoin-cli/src/app/v1.rs b/payjoin-cli/src/app/v1.rs index 4958afe24..2e344c4f0 100644 --- a/payjoin-cli/src/app/v1.rs +++ b/payjoin-cli/src/app/v1.rs @@ -378,6 +378,15 @@ fn try_contributing_inputs( let candidate_inputs = wallet.list_unspent().map_err(|e| ImplementationError::from(e.into_boxed_dyn_error()))?; + if candidate_inputs.is_empty() { + return Err(ImplementationError::from( + anyhow::anyhow!( + "No spendable UTXOs available in wallet. Please fund your wallet before resuming this session" + ) + .into_boxed_dyn_error(), + )); + } + let selected_input = payjoin.try_preserving_privacy(candidate_inputs).map_err(ImplementationError::new)?; diff --git a/payjoin-cli/src/app/v2/mod.rs b/payjoin-cli/src/app/v2/mod.rs index 5562dec33..6a7a73759 100644 --- a/payjoin-cli/src/app/v2/mod.rs +++ b/payjoin-cli/src/app/v2/mod.rs @@ -473,6 +473,12 @@ impl App { let wallet = self.wallet(); let candidate_inputs = wallet.list_unspent()?; + if candidate_inputs.is_empty() { + return Err(anyhow::anyhow!( + "No spendable UTXOs available in wallet. Cannot contribute inputs to payjoin." + )); + } + let selected_input = proposal.try_preserving_privacy(candidate_inputs)?; let proposal = proposal.contribute_inputs(vec![selected_input])?.commit_inputs().save(persister)?; diff --git a/payjoin-cli/src/app/wallet.rs b/payjoin-cli/src/app/wallet.rs index bebf9e44e..e15dfcaef 100644 --- a/payjoin-cli/src/app/wallet.rs +++ b/payjoin-cli/src/app/wallet.rs @@ -164,6 +164,12 @@ impl BitcoindWallet { Ok(unspent.into_iter().map(input_pair_from_corepc).collect()) } + /// Check if wallet has any spendable UTXOs + pub fn has_spendable_utxos(&self) -> Result { + let unspent = self.list_unspent()?; + Ok(!unspent.is_empty()) + } + /// Get the network this wallet is operating on pub fn network(&self) -> Result { tokio::task::block_in_place(|| {