]> Untitled Git - bdk-cli/commitdiff
refactor(handlers): Apply app context
authorVihiga Tyonum <withtvpeter@gmail.com>
Fri, 29 May 2026 03:24:41 +0000 (04:24 +0100)
committerVihiga Tyonum <withtvpeter@gmail.com>
Fri, 5 Jun 2026 16:32:14 +0000 (17:32 +0100)
- apply app context with state to all the modules
- cover case for commands when db is not required

src/handlers/config.rs
src/handlers/descriptor.rs
src/handlers/key.rs
src/handlers/offline.rs
src/handlers/online.rs
src/handlers/repl.rs

index fe64132f6ad97cb72f9d81f1c072c5df8dae2ba6..3bd302c07d244d3677d4ed65cca65c2671e16ae8 100644 (file)
@@ -10,6 +10,7 @@ use crate::client::ClientType;
 use crate::commands::WalletOpts;
 use crate::config::{WalletConfig, WalletConfigInner};
 use crate::error::BDKCliError as Error;
+use crate::handlers::Init;
 use crate::handlers::{AppCommand, AppContext};
 #[cfg(feature = "sqlite")]
 use crate::persister::DatabaseType;
@@ -27,10 +28,10 @@ pub struct SaveConfigCommand {
     pub(crate) wallet_opts: WalletOpts,
 }
 
-impl AppCommand for SaveConfigCommand {
+impl AppCommand<AppContext<Init>> for SaveConfigCommand {
     type Output = StatusResult;
 
-    fn execute(&self, ctx: &mut AppContext<'_>) -> Result<Self::Output, Error> {
+    fn execute(&self, ctx: &mut AppContext<Init>) -> Result<Self::Output, Error> {
         if ctx.network == Network::Bitcoin {
             eprintln!("WARNING: Configuring for Bitcoin MAINNET. Experimental software!");
         }
@@ -144,10 +145,10 @@ impl AppCommand for SaveConfigCommand {
 #[derive(Args, Debug, Clone, PartialEq)]
 pub struct ListWalletsCommand {}
 
-impl AppCommand for ListWalletsCommand {
+impl AppCommand<AppContext<Init>> for ListWalletsCommand {
     type Output = WalletsListResult;
 
-    fn execute(&self, ctx: &mut AppContext) -> Result<Self::Output, Error> {
+    fn execute(&self, ctx: &mut AppContext<Init>) -> Result<Self::Output, Error> {
         let config = match WalletConfig::load(&ctx.datadir)? {
             Some(cfg) => cfg,
             None => return Err(Error::Generic("No wallets configured yet.".into())),
index 188abd8168aaad7bd5d69a93771fbdbc40235151..c0bec73a6c2612f3b13362606a8b603e9f52111d 100644 (file)
@@ -1,3 +1,4 @@
+use crate::handlers::Init;
 use crate::utils::types::DescriptorResult;
 use crate::{
     error::BDKCliError as Error,
@@ -38,10 +39,10 @@ pub struct DescriptorCommand {
     /// Optional key: xprv, xpub, or mnemonic phrase
     key: Option<String>,
 }
-impl AppCommand for DescriptorCommand {
+impl AppCommand<AppContext<Init>> for DescriptorCommand {
     type Output = DescriptorResult;
 
-    fn execute(&self, ctx: &mut AppContext) -> Result<Self::Output, Error> {
+    fn execute(&self, ctx: &mut AppContext<Init>) -> Result<Self::Output, Error> {
         match &self.key {
             Some(key) => {
                 if is_mnemonic(key) {
@@ -68,10 +69,10 @@ pub struct CompileCommand {
 }
 
 #[cfg(feature = "compiler")]
-impl AppCommand for CompileCommand {
+impl AppCommand<AppContext<Init>> for CompileCommand {
     type Output = DescriptorResult;
 
-    fn execute(&self, _ctx: &mut AppContext) -> Result<Self::Output, Error> {
+    fn execute(&self, _ctx: &mut AppContext<Init>) -> Result<Self::Output, Error> {
         let policy: Concrete<String> = Concrete::from_str(&self.policy)
             .map_err(|e| Error::Generic(format!("Invalid policy: {e}")))?;
 
index b9dbe34ca045bfa8af495e205e4b71f08b7dc16b..ce8c844e5f1bdc36767f140e382341ca675529b6 100644 (file)
@@ -1,5 +1,6 @@
 use crate::commands::KeySubCommand;
 use crate::error::BDKCliError as Error;
+use crate::handlers::Init;
 use crate::handlers::{AppCommand, AppContext};
 use crate::utils::output::FormatOutput;
 use crate::utils::types::KeyResult;
@@ -15,7 +16,7 @@ use bdk_wallet::miniscript::{self, Segwitv0};
 use clap::Parser;
 
 impl KeySubCommand {
-    pub fn execute(&self, ctx: &mut AppContext) -> Result<(), Error> {
+    pub fn execute(&self, ctx: &mut AppContext<Init>) -> Result<(), Error> {
         match self {
             KeySubCommand::Generate(generate_key_command) => generate_key_command
                 .execute(ctx)?
@@ -44,10 +45,10 @@ pub struct GenerateKeyCommand {
     password: Option<String>,
 }
 
-impl AppCommand for GenerateKeyCommand {
+impl AppCommand<AppContext<Init>> for GenerateKeyCommand {
     type Output = KeyResult;
 
-    fn execute(&self, ctx: &mut AppContext) -> Result<Self::Output, Error> {
+    fn execute(&self, ctx: &mut AppContext<Init>) -> Result<Self::Output, Error> {
         let secp = Secp256k1::new();
         let mnemonic_type = match self.word_count {
             12 => WordCount::Words12,
@@ -88,10 +89,10 @@ pub struct DeriveKeyCommand {
     path: DerivationPath,
 }
 
-impl AppCommand for DeriveKeyCommand {
+impl AppCommand<AppContext<Init>> for DeriveKeyCommand {
     type Output = KeyResult;
 
-    fn execute(&self, ctx: &mut AppContext) -> Result<Self::Output, Error> {
+    fn execute(&self, ctx: &mut AppContext<Init>) -> Result<Self::Output, Error> {
         let secp = Secp256k1::new();
 
         let derived_xprv = &self.xprv.derive_priv(&secp, &self.path)?;
@@ -134,10 +135,10 @@ pub struct RestoreKeyCommand {
     password: Option<String>,
 }
 
-impl AppCommand for RestoreKeyCommand {
+impl AppCommand<AppContext<Init>> for RestoreKeyCommand {
     type Output = KeyResult;
 
-    fn execute(&self, ctx: &mut AppContext) -> Result<Self::Output, Error> {
+    fn execute(&self, ctx: &mut AppContext<Init>) -> Result<Self::Output, Error> {
         let secp = Secp256k1::new();
 
         let mnemonic = Mnemonic::parse_in(Language::English, &self.mnemonic)?;
index f541f74e2848e1225f2d54f1fd78db6931a01db5..c8bb18fb7dd7e26790b54d50d5294a99da70e348 100644 (file)
@@ -1,6 +1,6 @@
 use crate::commands::OfflineWalletSubCommand;
 use crate::error::BDKCliError as Error;
-use crate::handlers::{AppCommand, AppContext};
+use crate::handlers::{AppCommand, AppContext, OfflineOperations};
 use crate::utils::output::{FormatOutput, ListResult};
 use crate::utils::parse_address;
 use crate::utils::types::{
@@ -25,7 +25,7 @@ use {
 };
 
 impl OfflineWalletSubCommand {
-    pub fn execute(&self, ctx: &mut AppContext<'_>) -> Result<(), Error> {
+    pub fn execute(&self, ctx: &mut AppContext<OfflineOperations<'_>>) -> Result<(), Error> {
         match self {
             Self::NewAddress(new_address) => new_address.execute(ctx)?.write_out(std::io::stdout()),
             Self::Balance(balance) => balance.execute(ctx)?.write_out(std::io::stdout()),
@@ -75,14 +75,11 @@ impl OfflineWalletSubCommand {
 #[derive(Parser, Debug, Clone, PartialEq)]
 pub struct NewAddressCommand {}
 
-impl AppCommand for NewAddressCommand {
+impl AppCommand<AppContext<OfflineOperations<'_>>> for NewAddressCommand {
     type Output = AddressResult;
 
-    fn execute(&self, ctx: &mut AppContext) -> Result<Self::Output, Error> {
-        let wallet = ctx
-            .wallet
-            .as_deref_mut()
-            .ok_or(Error::Generic("Wallet required".to_string()))?;
+    fn execute(&self, ctx: &mut AppContext<OfflineOperations<'_>>) -> Result<Self::Output, Error> {
+        let wallet = &mut ctx.state.wallet;
         let address_info = wallet.reveal_next_address(KeychainKind::External);
         Ok(AddressResult::from(address_info))
     }
@@ -91,14 +88,11 @@ impl AppCommand for NewAddressCommand {
 #[derive(Parser, Debug, PartialEq, Clone)]
 pub struct UnusedAddressCommand {}
 
-impl AppCommand for UnusedAddressCommand {
+impl AppCommand<AppContext<OfflineOperations<'_>>> for UnusedAddressCommand {
     type Output = AddressResult;
 
-    fn execute(&self, ctx: &mut AppContext) -> Result<Self::Output, Error> {
-        let wallet = ctx
-            .wallet
-            .as_deref_mut()
-            .ok_or(Error::Generic("wallet is required".to_string()))?;
+    fn execute(&self, ctx: &mut AppContext<OfflineOperations<'_>>) -> Result<Self::Output, Error> {
+        let wallet = &mut ctx.state.wallet;
         let address_info = wallet.next_unused_address(KeychainKind::External);
         Ok(AddressResult::from(address_info))
     }
@@ -107,14 +101,11 @@ impl AppCommand for UnusedAddressCommand {
 #[derive(Parser, Debug, PartialEq, Clone)]
 pub struct UnspentCommand {}
 
-impl AppCommand for UnspentCommand {
+impl AppCommand<AppContext<OfflineOperations<'_>>> for UnspentCommand {
     type Output = ListResult<UnspentDetails>;
 
-    fn execute(&self, ctx: &mut AppContext) -> Result<Self::Output, Error> {
-        let wallet = ctx
-            .wallet
-            .as_deref_mut()
-            .ok_or(Error::Generic("Wallet is required".to_string()))?;
+    fn execute(&self, ctx: &mut AppContext<OfflineOperations<'_>>) -> Result<Self::Output, Error> {
+        let wallet = &mut ctx.state.wallet;
         let utxos = wallet
             .list_unspent()
             .map(|utxo| UnspentDetails::from_local_output(&utxo, ctx.network))
@@ -127,16 +118,11 @@ impl AppCommand for UnspentCommand {
 #[derive(Parser, Debug, PartialEq, Clone)]
 pub struct TransactionsCommand {}
 
-impl AppCommand for TransactionsCommand {
+impl AppCommand<AppContext<OfflineOperations<'_>>> for TransactionsCommand {
     type Output = ListResult<TransactionDetails>;
 
-    fn execute(&self, ctx: &mut AppContext) -> Result<Self::Output, Error> {
-        let wallet = ctx
-            .wallet
-            .as_deref_mut()
-            .ok_or(Error::Generic("wallet required".to_string()))?;
-
-        let transactions = wallet.transactions();
+    fn execute(&self, ctx: &mut AppContext<OfflineOperations<'_>>) -> Result<Self::Output, Error> {
+        let transactions = &mut ctx.state.wallet.transactions();
 
         let txns: Vec<TransactionDetails> = transactions
             .map(|tx| {
@@ -170,15 +156,11 @@ impl AppCommand for TransactionsCommand {
 #[derive(Parser, Debug, PartialEq, Clone)]
 pub struct BalanceCommand {}
 
-impl AppCommand for BalanceCommand {
+impl AppCommand<AppContext<OfflineOperations<'_>>> for BalanceCommand {
     type Output = BalanceResult;
 
-    fn execute(&self, ctx: &mut AppContext) -> Result<Self::Output, Error> {
-        let wallet = ctx
-            .wallet
-            .as_deref_mut()
-            .ok_or_else(|| Error::Generic("Wallet required".into()))?;
-        let balance = wallet.balance();
+    fn execute(&self, ctx: &mut AppContext<OfflineOperations<'_>>) -> Result<Self::Output, Error> {
+        let balance = ctx.state.wallet.balance();
         Ok(BalanceResult::from(balance))
     }
 }
@@ -239,16 +221,11 @@ pub struct CreateTxCommand {
     pub add_data: Option<String>,
 }
 
-impl AppCommand for CreateTxCommand {
+impl AppCommand<AppContext<OfflineOperations<'_>>> for CreateTxCommand {
     type Output = PsbtResult;
 
-    fn execute(&self, ctx: &mut AppContext) -> Result<Self::Output, Error> {
-        let wallet = ctx
-            .wallet
-            .as_deref_mut()
-            .ok_or_else(|| Error::Generic("Wallet required".into()))?;
-
-        let mut tx_builder = wallet.build_tx();
+    fn execute(&self, ctx: &mut AppContext<OfflineOperations<'_>>) -> Result<Self::Output, Error> {
+        let mut tx_builder = ctx.state.wallet.build_tx();
 
         if self.send_all {
             tx_builder
@@ -348,14 +325,11 @@ pub struct BumpFeeCommand {
     pub fee_rate: f32,
 }
 
-impl AppCommand for BumpFeeCommand {
+impl AppCommand<AppContext<OfflineOperations<'_>>> for BumpFeeCommand {
     type Output = PsbtResult;
 
-    fn execute(&self, ctx: &mut AppContext) -> Result<Self::Output, Error> {
-        let wallet = ctx
-            .wallet
-            .as_deref_mut()
-            .ok_or_else(|| Error::Generic("Wallet required".into()))?;
+    fn execute(&self, ctx: &mut AppContext<OfflineOperations<'_>>) -> Result<Self::Output, Error> {
+        let wallet = &mut ctx.state.wallet;
 
         let txid = Txid::from_str(self.txid.as_str())?;
 
@@ -392,14 +366,11 @@ impl AppCommand for BumpFeeCommand {
 #[derive(Parser, Debug, PartialEq, Clone)]
 pub struct PoliciesCommand {}
 
-impl AppCommand for PoliciesCommand {
+impl AppCommand<AppContext<OfflineOperations<'_>>> for PoliciesCommand {
     type Output = KeychainPair<serde_json::Value>;
 
-    fn execute(&self, ctx: &mut AppContext) -> Result<Self::Output, Error> {
-        let wallet = ctx
-            .wallet
-            .as_deref_mut()
-            .ok_or(Error::Generic("Wallet required".into()))?;
+    fn execute(&self, ctx: &mut AppContext<OfflineOperations<'_>>) -> Result<Self::Output, Error> {
+        let wallet = &mut ctx.state.wallet;
         let external_policy = wallet.policies(KeychainKind::External)?;
         let internal_policy = wallet.policies(KeychainKind::Internal)?;
 
@@ -413,14 +384,11 @@ impl AppCommand for PoliciesCommand {
 #[derive(Parser, Debug, PartialEq, Clone)]
 pub struct PublicDescriptorCommand {}
 
-impl AppCommand for PublicDescriptorCommand {
+impl AppCommand<AppContext<OfflineOperations<'_>>> for PublicDescriptorCommand {
     type Output = KeychainPair<String>;
 
-    fn execute(&self, ctx: &mut AppContext) -> Result<Self::Output, Error> {
-        let wallet = ctx
-            .wallet
-            .as_deref_mut()
-            .ok_or(Error::Generic("Wallet required".into()))?;
+    fn execute(&self, ctx: &mut AppContext<OfflineOperations<'_>>) -> Result<Self::Output, Error> {
+        let wallet = &mut ctx.state.wallet;
         Ok(KeychainPair {
             external: wallet.public_descriptor(KeychainKind::External).to_string(),
             internal: wallet.public_descriptor(KeychainKind::Internal).to_string(),
@@ -443,14 +411,11 @@ pub struct SignCommand {
     pub trust_witness_utxo: Option<bool>,
 }
 
-impl AppCommand for SignCommand {
+impl AppCommand<AppContext<OfflineOperations<'_>>> for SignCommand {
     type Output = PsbtResult;
 
-    fn execute(&self, ctx: &mut AppContext) -> Result<Self::Output, Error> {
-        let wallet = ctx
-            .wallet
-            .as_deref_mut()
-            .ok_or(Error::Generic("Wallet required".into()))?;
+    fn execute(&self, ctx: &mut AppContext<OfflineOperations<'_>>) -> Result<Self::Output, Error> {
+        let wallet = &mut ctx.state.wallet;
         let psbt_bytes = BASE64_STANDARD
             .decode(&self.psbt)
             .map_err(|e| Error::Generic(e.to_string()))?;
@@ -473,10 +438,10 @@ pub struct ExtractPsbtCommand {
     pub psbt: String,
 }
 
-impl AppCommand for ExtractPsbtCommand {
+impl AppCommand<AppContext<OfflineOperations<'_>>> for ExtractPsbtCommand {
     type Output = RawPsbt;
 
-    fn execute(&self, _ctx: &mut AppContext) -> Result<Self::Output, Error> {
+    fn execute(&self, _ctx: &mut AppContext<OfflineOperations<'_>>) -> Result<Self::Output, Error> {
         let psbt_serialized = BASE64_STANDARD.decode(self.psbt.clone())?;
         let psbt = Psbt::deserialize(&psbt_serialized)?;
         let raw_tx = psbt.extract_tx()?;
@@ -500,14 +465,11 @@ pub struct FinalizePsbtCommand {
     pub trust_witness_utxo: Option<bool>,
 }
 
-impl AppCommand for FinalizePsbtCommand {
+impl AppCommand<AppContext<OfflineOperations<'_>>> for FinalizePsbtCommand {
     type Output = PsbtResult;
 
-    fn execute(&self, ctx: &mut AppContext) -> Result<Self::Output, Error> {
-        let wallet = ctx
-            .wallet
-            .as_deref_mut()
-            .ok_or(Error::Generic("Wallet required".into()))?;
+    fn execute(&self, ctx: &mut AppContext<OfflineOperations<'_>>) -> Result<Self::Output, Error> {
+        let wallet = &mut ctx.state.wallet;
         let psbt_bytes = BASE64_STANDARD
             .decode(&self.psbt)
             .map_err(|e| Error::Generic(e.to_string()))?;
@@ -532,10 +494,10 @@ pub struct CombinePsbtCommand {
     pub psbt: Vec<String>,
 }
 
-impl AppCommand for CombinePsbtCommand {
+impl AppCommand<AppContext<OfflineOperations<'_>>> for CombinePsbtCommand {
     type Output = PsbtResult;
 
-    fn execute(&self, _ctx: &mut AppContext) -> Result<Self::Output, Error> {
+    fn execute(&self, _ctx: &mut AppContext<OfflineOperations<'_>>) -> Result<Self::Output, Error> {
         let mut psbts = self
             .psbt
             .iter()
@@ -560,7 +522,7 @@ impl AppCommand for CombinePsbtCommand {
     }
 }
 
-#[cfg(feature = "bip322")]
+// #[cfg(feature = "bip322")]
 #[derive(Debug, Parser, Clone, PartialEq)]
 pub struct SignMessageCommand {
     /// The message to sign
@@ -581,14 +543,11 @@ pub struct SignMessageCommand {
 }
 
 #[cfg(feature = "bip322")]
-impl AppCommand for SignMessageCommand {
+impl AppCommand<AppContext<OfflineOperations<'_>>> for SignMessageCommand {
     type Output = MessageResult;
 
-    fn execute(&self, ctx: &mut AppContext) -> Result<Self::Output, Error> {
-        let wallet = ctx
-            .wallet
-            .as_deref_mut()
-            .ok_or(Error::Generic("Wallet required".into()))?;
+    fn execute(&self, ctx: &mut AppContext<OfflineOperations<'_>>) -> Result<Self::Output, Error> {
+        let wallet = &mut ctx.state.wallet;
         let address: Address = parse_address(&self.address)?;
         let signature_format = parse_signature_format(&self.signature_type)?;
 
@@ -630,14 +589,11 @@ pub struct VerifyMessageCommand {
 }
 
 #[cfg(feature = "bip322")]
-impl AppCommand for VerifyMessageCommand {
+impl AppCommand<AppContext<OfflineOperations<'_>>> for VerifyMessageCommand {
     type Output = MessageResult;
 
-    fn execute(&self, ctx: &mut AppContext) -> Result<Self::Output, Error> {
-        let wallet = ctx
-            .wallet
-            .as_deref_mut()
-            .ok_or(Error::Generic("Wallet required".into()))?;
+    fn execute(&self, ctx: &mut AppContext<OfflineOperations<'_>>) -> Result<Self::Output, Error> {
+        let wallet = &ctx.state.wallet;
 
         let address: Address = parse_address(&self.address)?;
 
index 866f6f94baf01efa27732fa95341883ab4fea07d..0c44c34218e57ac39565bbc070c820849e3b00ab 100644 (file)
@@ -25,14 +25,14 @@ use {bdk_wallet::KeychainKind, std::collections::HashSet, std::io::Write};
 use {
     crate::commands::OnlineWalletSubCommand,
     crate::error::BDKCliError as Error,
-    crate::handlers::{AppContext, AsyncCommand},
+    crate::handlers::{AppContext, AsyncAppCommand, OnlineOperations},
     crate::payjoin::{PayjoinManager, ohttp::RelayManager},
     crate::utils::is_final,
     crate::utils::output::FormatOutput,
     crate::utils::types::{StatusResult, TransactionResult},
     bdk_wallet::bitcoin::{
-        Psbt, Transaction, base64::Engine, base64::prelude::BASE64_STANDARD, consensus::Decodable,
-        hex::FromHex,
+        Psbt, Transaction, Txid, base64::Engine, base64::prelude::BASE64_STANDARD,
+        consensus::Decodable, hex::FromHex,
     },
     std::sync::{Arc, Mutex},
 };
@@ -43,7 +43,7 @@ use {
     feature = "rpc"
 ))]
 impl OnlineWalletSubCommand {
-    pub async fn execute(&self, ctx: &mut AppContext<'_>) -> Result<(), Error> {
+    pub async fn execute(&self, ctx: &mut AppContext<OnlineOperations<'_>>) -> Result<(), Error> {
         match self {
             OnlineWalletSubCommand::FullScan(full_scan_command) => {
                 let response: StatusResult = full_scan_command.execute(ctx).await?;
@@ -84,17 +84,15 @@ pub struct FullScanCommand {
     feature = "cbf",
     feature = "rpc"
 ))]
-impl AsyncCommand for FullScanCommand {
+impl AsyncAppCommand<AppContext<OnlineOperations<'_>>> for FullScanCommand {
     type Output = StatusResult;
 
-    async fn execute(&self, ctx: &mut AppContext<'_>) -> Result<Self::Output, Error> {
-        let wallet = ctx
-            .wallet
-            .as_deref_mut()
-            .ok_or(Error::Generic("Wallet required".into()))?;
-        let client = ctx
-            .client
-            .ok_or(Error::Generic("Online client required".into()))?;
+    async fn execute(
+        &self,
+        ctx: &mut AppContext<OnlineOperations<'_>>,
+    ) -> Result<Self::Output, Error> {
+        let wallet = &mut ctx.state.wallet;
+        let client = ctx.state.client;
 
         #[cfg(any(feature = "electrum", feature = "esplora"))]
         let request = wallet.start_full_scan().inspect({
@@ -185,17 +183,15 @@ pub struct SyncCommand {}
     feature = "cbf",
     feature = "rpc"
 ))]
-impl AsyncCommand for SyncCommand {
+impl AsyncAppCommand<AppContext<OnlineOperations<'_>>> for SyncCommand {
     type Output = StatusResult;
 
-    async fn execute(&self, ctx: &mut AppContext<'_>) -> Result<Self::Output, Error> {
-        let wallet = ctx
-            .wallet
-            .as_deref_mut()
-            .ok_or(Error::Generic("Wallet required".to_string()))?;
-        let client = ctx
-            .client
-            .ok_or(Error::Generic("Client is required".to_string()))?;
+    async fn execute(
+        &self,
+        ctx: &mut AppContext<OnlineOperations<'_>>,
+    ) -> Result<Self::Output, Error> {
+        let mut wallet = &mut ctx.state.wallet;
+        let client = ctx.state.client;
         #[cfg(any(feature = "electrum", feature = "esplora"))]
         let request = wallet
             .start_sync_with_revealed_spks()
@@ -271,8 +267,8 @@ impl AsyncCommand for SyncCommand {
                 let mempool_txs = emitter.mempool()?;
                 wallet.apply_unconfirmed_txs(mempool_txs.update);
             }
-            #[cfg(feature = "cbf")]
-            KyotoClient { client } => sync_kyoto_client(wallet, client)
+            // #[cfg(feature = "cbf")]
+            KyotoClient { client } => sync_kyoto_client(&mut wallet, client)
                 .await
                 .map_err(|e| Error::Generic(e.to_string()))?,
         }
@@ -306,13 +302,14 @@ pub struct BroadcastCommand {
     feature = "cbf",
     feature = "rpc"
 ))]
-impl AsyncCommand for BroadcastCommand {
+impl AsyncAppCommand<AppContext<OnlineOperations<'_>>> for BroadcastCommand {
     type Output = TransactionResult;
 
-    async fn execute(&self, ctx: &mut AppContext<'_>) -> Result<Self::Output, Error> {
-        let client = ctx
-            .client
-            .ok_or(Error::Generic("Online client required".into()))?;
+    async fn execute(
+        &self,
+        ctx: &mut AppContext<OnlineOperations<'_>>,
+    ) -> Result<Self::Output, Error> {
+        let client = ctx.state.client;
 
         let tx = match (&self.psbt, &self.tx) {
             (Some(psbt), None) => {
@@ -339,7 +336,7 @@ impl AsyncCommand for BroadcastCommand {
             }
         };
 
-        let txid = client.broadcast(tx).await?;
+        let txid: Txid = client.broadcast(tx).await?;
 
         Ok(TransactionResult {
             txid: txid.to_string(),
@@ -349,20 +346,6 @@ impl AsyncCommand for BroadcastCommand {
 
 #[derive(Parser, Debug, Clone, PartialEq, Eq)]
 pub struct ReceivePayjoinCommand {
-    // /// The amount to receive in satoshis.
-    // pub amount: u64,
-
-    // /// The payjoin directory URL.
-    // #[clap(long)]
-    // pub directory: String,
-
-    // /// Optional OHTTP relay URLs for privacy.
-    // #[clap(long)]
-    // pub ohttp_relays: Vec<String>,
-
-    // /// Maximum fee rate in sat/vB to pay for the payjoin transaction.
-    // #[clap(long)]
-    // pub max_fee_rate: u64,
     /// Amount to be received in sats.
     #[arg(env = "PAYJOIN_AMOUNT", long = "amount", required = true)]
     amount: u64,
@@ -385,17 +368,15 @@ pub struct ReceivePayjoinCommand {
     feature = "cbf",
     feature = "rpc"
 ))]
-impl AsyncCommand for ReceivePayjoinCommand {
+impl AsyncAppCommand<AppContext<OnlineOperations<'_>>> for ReceivePayjoinCommand {
     type Output = StatusResult;
 
-    async fn execute(&self, ctx: &mut AppContext<'_>) -> Result<Self::Output, Error> {
-        let wallet = ctx
-            .wallet
-            .as_deref_mut()
-            .ok_or(Error::Generic("Wallet required".into()))?;
-        let client = ctx
-            .client
-            .ok_or(Error::Generic("Online client required".into()))?;
+    async fn execute(
+        &self,
+        ctx: &mut AppContext<OnlineOperations<'_>>,
+    ) -> Result<Self::Output, Error> {
+        let wallet = &mut ctx.state.wallet;
+        let client = ctx.state.client;
 
         let relay_manager = Arc::new(Mutex::new(RelayManager::new()));
         let mut payjoin_manager = PayjoinManager::new(wallet, relay_manager);
@@ -415,16 +396,6 @@ impl AsyncCommand for ReceivePayjoinCommand {
 
 #[derive(Parser, Debug, Clone, PartialEq, Eq)]
 pub struct SendPayjoinCommand {
-    // /// The Payjoin URI string to send to.
-    // pub uri: String,
-
-    // /// Optional OHTTP relay URLs for privacy.
-    // #[clap(long)]
-    // pub ohttp_relays: Vec<String>,
-
-    // /// Fee rate in sat/vB for the transaction.
-    // #[clap(long, short)]
-    // pub fee_rate: u64,
     /// BIP 21 URI for the Payjoin.
     #[arg(env = "PAYJOIN_URI", long = "uri", required = true)]
     uri: String,
@@ -447,15 +418,15 @@ pub struct SendPayjoinCommand {
     feature = "cbf",
     feature = "rpc"
 ))]
-impl AsyncCommand for SendPayjoinCommand {
+impl AsyncAppCommand<AppContext<OnlineOperations<'_>>> for SendPayjoinCommand {
     type Output = StatusResult;
 
-    async fn execute(&self, ctx: &mut AppContext<'_>) -> Result<Self::Output, Error> {
-        let wallet = ctx
-            .wallet
-            .as_deref_mut()
-            .ok_or(Error::Generic("Wallet required".into()))?;
-        let client = ctx.client.ok_or(Error::Generic("client required".into()))?;
+    async fn execute(
+        &self,
+        ctx: &mut AppContext<OnlineOperations<'_>>,
+    ) -> Result<Self::Output, Error> {
+        let wallet = &mut ctx.state.wallet;
+        let client = ctx.state.client;
 
         let relay_manager = Arc::new(Mutex::new(RelayManager::new()));
         let mut payjoin_manager = PayjoinManager::new(wallet, relay_manager);
index 19206b251e7c49c23ca2d379463ab7f7a65d8d88..802e7358ad10f87db97554522c55b379fd1c8412 100644 (file)
@@ -1,12 +1,21 @@
 use bdk_wallet::{Wallet, bitcoin::Network};
 
 #[cfg(feature = "repl")]
-use crate::handlers::{AppCommand, AppContext};
-use crate::utils::output::FormatOutput;
+use crate::handlers::AppCommand;
+#[cfg(feature = "repl")]
+use crate::{handlers::AppContext, utils::output::FormatOutput};
 
-#[cfg(feature = "sqlite")]
+#[cfg(feature = "repl")]
 use crate::commands::ReplSubCommand;
 use clap::Parser;
+
+#[cfg(any(
+    feature = "electrum",
+    feature = "esplora",
+    feature = "rpc",
+    feature = "cbf"
+))]
+use crate::client::BlockchainClient;
 #[cfg(feature = "repl")]
 use std::io::Write;
 use {
@@ -18,9 +27,16 @@ use {
 pub(crate) async fn respond(
     network: Network,
     wallet: &mut Wallet,
+    #[cfg(any(
+        feature = "electrum",
+        feature = "esplora",
+        feature = "rpc",
+        feature = "cbf"
+    ))]
+    client: Option<&BlockchainClient>,
     line: &str,
     datadir: std::path::PathBuf,
-    cli_opts: &CliOpts,
+    _cli_opts: &CliOpts,
 ) -> Result<bool, String> {
     let args = shlex::split(line).ok_or("error: Invalid quoting".to_string())?;
 
@@ -35,12 +51,14 @@ pub(crate) async fn respond(
         }
     };
 
-    let mut ctx = AppContext::new(network, datadir.clone()).with_wallet(&mut *wallet);
-
     let response = match repl_subcommand {
         ReplSubCommand::Wallet { subcommand } => match subcommand {
             WalletSubCommand::OfflineWalletSubCommand(cmd) => {
-                cmd.execute(&mut ctx).map_err(|e| e.to_string())?;
+                let mut ctx = AppContext::new_offline_wallet(network, datadir, wallet);
+                cmd.execute(&mut ctx)
+                    .map_err(|e| e.to_string())?
+                    .write_out(std::io::stdout())
+                    .map_err(|e| e.to_string())?;
                 Some(())
             }
             #[cfg(any(
@@ -50,35 +68,47 @@ pub(crate) async fn respond(
                 feature = "rpc"
             ))]
             WalletSubCommand::OnlineWalletSubCommand(cmd) => {
-                let value = cmd.execute(&mut ctx).await.map_err(|e| e.to_string())?;
+                let client_ref = client.ok_or("Online commands require a client.".to_string())?;
+                let mut ctx = AppContext::new_online_wallet(network, datadir, wallet, client_ref);
+
+                cmd.execute(&mut ctx)
+                    .await
+                    .map_err(|e| e.to_string())?
+                    .write_out(std::io::stdout())
+                    .map_err(|e| e.to_string())?;
                 Some(())
-                // Some(value)
             }
             WalletSubCommand::Config(config_cmd) => {
                 let mut ctx = AppContext::new(network, datadir);
-                let _ = config_cmd
+                config_cmd
                     .execute(&mut ctx)
                     .map_err(|e| e.to_string())?
-                    .write_out(std::io::stdout());
+                    .write_out(std::io::stdout())
+                    .map_err(|e| e.to_string())?;
                 Some(())
             }
         },
 
-        // Assuming your REPL Descriptor command is an inline struct based on commands.rs
         ReplSubCommand::Descriptor(cmd) => {
-            let value = cmd
-                .execute(&mut ctx)
+            let mut ctx = AppContext::new(network, datadir);
+            cmd.execute(&mut ctx)
                 .map_err(|e| e.to_string())?
-                .write_out(std::io::stdout());
+                .write_out(std::io::stdout())
+                .map_err(|e| e.to_string())?;
+            Some(())
+        }
+
+        ReplSubCommand::Key { subcommand } => {
+            let mut ctx = AppContext::new(network, datadir);
+            subcommand.execute(&mut ctx).map_err(|e| e.to_string())?;
             Some(())
         }
 
         ReplSubCommand::Exit => None,
-        _ => todo!(),
+        // _ => todo!(), // Handled specifically depending on your ReplSubCommand definitions
     };
 
-    if let Some(value) = response {
-        // writeln!(std::io::stdout(), "{value}").map_err(|e| e.to_string())?;
+    if response.is_some() {
         std::io::stdout().flush().map_err(|e| e.to_string())?;
         Ok(false)
     } else {