key::{DeriveKeyCommand, GenerateKeyCommand, RestoreKeyCommand},
offline::{
BalanceCommand, BumpFeeCommand, CombinePsbtCommand, CreateTxCommand, ExtractPsbtCommand,
- FinalizePsbtCommand, NewAddressCommand, PoliciesCommand, PublicDescriptorCommand,
- SignCommand, TransactionsCommand, UnspentCommand, UnusedAddressCommand,
+ FinalizePsbtCommand, LockUtxoCommand, LockedUtxosCommand, NewAddressCommand,
+ PoliciesCommand, PublicDescriptorCommand, SignCommand, TransactionsCommand,
+ UnlockUtxoCommand, UnspentCommand, UnusedAddressCommand,
},
};
/// Verify a BIP322 signature
#[cfg(feature = "bip322")]
VerifyMessage(VerifyMessageCommand),
+ /// Lock UTXO(s) so they're excluded from coin selection.
+ LockUtxo(LockUtxoCommand),
+ /// Unlock previously locked UTXO(s).
+ UnlockUtxo(UnlockUtxoCommand),
+ /// List currently locked UTXOs.
+ LockedUtxos(LockedUtxosCommand),
}
/// Wallet subcommands that needs a blockchain backend.
Self::VerifyMessage(verify_message_command) => verify_message_command
.execute(ctx)?
.write_out(std::io::stdout()),
+ Self::LockUtxo(lock_utxo) => lock_utxo.execute(ctx)?.write_out(std::io::stdout()),
+ Self::UnlockUtxo(unlock_utxo) => unlock_utxo.execute(ctx)?.write_out(std::io::stdout()),
+ Self::LockedUtxos(locked_utxos) => {
+ locked_utxos.execute(ctx)?.write_out(std::io::stdout())
+ }
}
}
}
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))
+ let outputs: Vec<_> = wallet.list_unspent().collect();
+ let utxos = outputs
+ .into_iter()
+ .map(|utxo| {
+ let is_locked = wallet.is_outpoint_locked(utxo.outpoint);
+ UnspentDetails::from_local_output(&utxo, ctx.network, is_locked)
+ })
.collect();
Ok(ListResult::new(utxos))
})
}
}
+
+#[derive(Parser, Debug, Clone, PartialEq)]
+pub struct LockUtxoCommand {
+ /// Outpoint(s) to lock, format TXID:VOUT.
+ #[arg(env = "TXID:VOUT", long = "utxo", required = true, value_parser = parse_outpoint)]
+ pub utxos: Vec<OutPoint>,
+}
+
+impl AppCommand<AppContext<OfflineOperations<'_>>> for LockUtxoCommand {
+ type Output = ListResult<String>;
+ fn execute(&self, ctx: &mut AppContext<OfflineOperations<'_>>) -> Result<Self::Output, Error> {
+ let wallet = &mut ctx.state.wallet;
+ for out_point in &self.utxos {
+ if wallet.get_utxo(*out_point).is_none() {
+ eprintln!("warning: {out_point} is not a known wallet UTXO; locking anyway");
+ }
+ wallet.lock_outpoint(*out_point);
+ }
+ let locked = wallet
+ .list_locked_outpoints()
+ .map(|o| o.to_string())
+ .collect();
+ Ok(ListResult::new(locked))
+ }
+}
+
+#[derive(Parser, Debug, Clone, PartialEq)]
+pub struct UnlockUtxoCommand {
+ /// Outpoint(s) to unlock, format TXID:VOUT.
+ #[arg(env = "TXID:VOUT", long = "utxo", required = true, value_parser = parse_outpoint)]
+ pub utxos: Vec<OutPoint>,
+}
+
+impl AppCommand<AppContext<OfflineOperations<'_>>> for UnlockUtxoCommand {
+ type Output = ListResult<String>;
+ fn execute(&self, ctx: &mut AppContext<OfflineOperations<'_>>) -> Result<Self::Output, Error> {
+ let wallet = &mut ctx.state.wallet;
+ for out_point in &self.utxos {
+ wallet.unlock_outpoint(*out_point);
+ }
+ let locked = wallet
+ .list_locked_outpoints()
+ .map(|o| o.to_string())
+ .collect();
+ Ok(ListResult::new(locked))
+ }
+}
+
+#[derive(Parser, Debug, Clone, PartialEq)]
+pub struct LockedUtxosCommand {}
+
+impl AppCommand<AppContext<OfflineOperations<'_>>> for LockedUtxosCommand {
+ type Output = ListResult<String>;
+ fn execute(&self, ctx: &mut AppContext<OfflineOperations<'_>>) -> Result<Self::Output, Error> {
+ let wallet = &mut ctx.state.wallet;
+ let locked = wallet
+ .list_locked_outpoints()
+ .map(|o| o.to_string())
+ .collect();
+ Ok(ListResult::new(locked))
+ }
+}