2020-08-31 11:26:36 +02:00
// Magical Bitcoin Library
// Written in 2020 by
// Alekos Filini <alekos.filini@gmail.com>
//
// Copyright (c) 2020 Magical Bitcoin
//
// Permission is hereby granted, free of charge, to any person obtaining a copy
// of this software and associated documentation files (the "Software"), to deal
// in the Software without restriction, including without limitation the rights
// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
// copies of the Software, and to permit persons to whom the Software is
// furnished to do so, subject to the following conditions:
//
// The above copyright notice and this permission notice shall be included in all
// copies or substantial portions of the Software.
//
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
// SOFTWARE.
2020-05-16 16:48:31 +02:00
use std ::collections ::BTreeMap ;
2020-05-08 23:30:45 +02:00
use std ::str ::FromStr ;
use clap ::{ App , Arg , ArgMatches , SubCommand } ;
#[ allow(unused_imports) ]
use log ::{ debug , error , info , trace , LevelFilter } ;
use bitcoin ::consensus ::encode ::{ deserialize , serialize , serialize_hex } ;
2020-08-15 20:16:34 +02:00
use bitcoin ::hashes ::hex ::FromHex ;
2020-05-08 23:30:45 +02:00
use bitcoin ::util ::psbt ::PartiallySignedTransaction ;
2020-09-04 15:45:11 +02:00
use bitcoin ::{ Address , OutPoint , Script , Txid } ;
2020-05-08 23:30:45 +02:00
2020-08-25 16:07:26 +02:00
use crate ::blockchain ::log_progress ;
2020-05-08 23:30:45 +02:00
use crate ::error ::Error ;
use crate ::types ::ScriptType ;
2020-08-07 11:23:01 +02:00
use crate ::{ FeeRate , TxBuilder , Wallet } ;
2020-05-08 23:30:45 +02:00
2020-09-04 15:45:11 +02:00
fn parse_recipient ( s : & str ) -> Result < ( Script , u64 ) , String > {
2020-10-28 15:34:46 -07:00
let parts : Vec < _ > = s . split ( ':' ) . collect ( ) ;
2020-05-08 23:30:45 +02:00
if parts . len ( ) ! = 2 {
return Err ( " Invalid format " . to_string ( ) ) ;
}
let addr = Address ::from_str ( & parts [ 0 ] ) ;
if let Err ( e ) = addr {
return Err ( format! ( " {:?} " , e ) ) ;
}
let val = u64 ::from_str ( & parts [ 1 ] ) ;
if let Err ( e ) = val {
return Err ( format! ( " {:?} " , e ) ) ;
}
2020-09-04 15:45:11 +02:00
Ok ( ( addr . unwrap ( ) . script_pubkey ( ) , val . unwrap ( ) ) )
2020-05-08 23:30:45 +02:00
}
fn parse_outpoint ( s : & str ) -> Result < OutPoint , String > {
OutPoint ::from_str ( s ) . map_err ( | e | format! ( " {:?} " , e ) )
}
2020-08-31 10:49:44 +02:00
fn recipient_validator ( s : String ) -> Result < ( ) , String > {
parse_recipient ( & s ) . map ( | _ | ( ) )
2020-05-08 23:30:45 +02:00
}
fn outpoint_validator ( s : String ) -> Result < ( ) , String > {
parse_outpoint ( & s ) . map ( | _ | ( ) )
}
pub fn make_cli_subcommands < ' a , ' b > ( ) -> App < ' a , ' b > {
App ::new ( " Magical Bitcoin Wallet " )
. version ( option_env! ( " CARGO_PKG_VERSION " ) . unwrap_or ( " unknown " ) )
. author ( option_env! ( " CARGO_PKG_AUTHORS " ) . unwrap_or ( " " ) )
. about ( " A modern, lightweight, descriptor-based wallet " )
. subcommand (
SubCommand ::with_name ( " get_new_address " ) . about ( " Generates a new external address " ) ,
)
2020-10-30 14:43:36 +01:00
. subcommand ( SubCommand ::with_name ( " sync " ) . about ( " Syncs with the chosen Electrum server " ) . arg (
Arg ::with_name ( " max_addresses " )
. required ( false )
. takes_value ( true )
. long ( " max_addresses " )
. help ( " max addresses to consider " ) ,
) )
2020-05-08 23:30:45 +02:00
. subcommand (
SubCommand ::with_name ( " list_unspent " ) . about ( " Lists the available spendable UTXOs " ) ,
)
2020-07-21 18:37:15 +02:00
. subcommand (
SubCommand ::with_name ( " list_transactions " ) . about ( " Lists all the incoming and outgoing transactions of the wallet " ) ,
)
2020-05-08 23:30:45 +02:00
. subcommand (
SubCommand ::with_name ( " get_balance " ) . about ( " Returns the current wallet balance " ) ,
)
. subcommand (
SubCommand ::with_name ( " create_tx " )
. about ( " Creates a new unsigned tranasaction " )
. arg (
Arg ::with_name ( " to " )
. long ( " to " )
. value_name ( " ADDRESS:SAT " )
2020-08-31 10:49:44 +02:00
. help ( " Adds a recipient to the transaction " )
2020-05-08 23:30:45 +02:00
. takes_value ( true )
. number_of_values ( 1 )
. required ( true )
. multiple ( true )
2020-08-31 10:49:44 +02:00
. validator ( recipient_validator ) ,
2020-05-08 23:30:45 +02:00
)
. arg (
Arg ::with_name ( " send_all " )
. short ( " all " )
. long ( " send_all " )
2020-08-31 10:49:44 +02:00
. help ( " Sends all the funds (or all the selected utxos). Requires only one recipients of value 0 " ) ,
2020-05-08 23:30:45 +02:00
)
2020-08-13 16:51:27 +02:00
. arg (
Arg ::with_name ( " enable_rbf " )
. short ( " rbf " )
. long ( " enable_rbf " )
. help ( " Enables Replace-By-Fee (BIP125) " ) ,
)
2020-05-08 23:30:45 +02:00
. arg (
Arg ::with_name ( " utxos " )
. long ( " utxos " )
. value_name ( " TXID:VOUT " )
. help ( " Selects which utxos *must* be spent " )
. takes_value ( true )
. number_of_values ( 1 )
. multiple ( true )
. validator ( outpoint_validator ) ,
)
. arg (
Arg ::with_name ( " unspendable " )
. long ( " unspendable " )
. value_name ( " TXID:VOUT " )
. help ( " Marks an utxo as unspendable " )
. takes_value ( true )
. number_of_values ( 1 )
. multiple ( true )
. validator ( outpoint_validator ) ,
)
. arg (
Arg ::with_name ( " fee_rate " )
. short ( " fee " )
. long ( " fee_rate " )
. value_name ( " SATS_VBYTE " )
. help ( " Fee rate to use in sat/vbyte " )
. takes_value ( true ) ,
)
. arg (
Arg ::with_name ( " policy " )
. long ( " policy " )
. value_name ( " POLICY " )
2020-05-16 16:48:31 +02:00
. help ( " Selects which policy should be used to satisfy the descriptor " )
2020-05-08 23:30:45 +02:00
. takes_value ( true )
. number_of_values ( 1 ) ,
) ,
)
2020-08-13 16:51:27 +02:00
. subcommand (
SubCommand ::with_name ( " bump_fee " )
. about ( " Bumps the fees of an RBF transaction " )
. arg (
Arg ::with_name ( " txid " )
. required ( true )
. takes_value ( true )
. short ( " txid " )
. long ( " txid " )
. help ( " TXID of the transaction to update " ) ,
)
. arg (
Arg ::with_name ( " send_all " )
. short ( " all " )
. long ( " send_all " )
. help ( " Allows the wallet to reduce the amount of the only output in order to increase fees. This is generally the expected behavior for transactions originally created with `send_all` " ) ,
)
. arg (
Arg ::with_name ( " utxos " )
. long ( " utxos " )
. value_name ( " TXID:VOUT " )
. help ( " Selects which utxos *must* be added to the tx. Unconfirmed utxos cannot be used " )
. takes_value ( true )
. number_of_values ( 1 )
. multiple ( true )
. validator ( outpoint_validator ) ,
)
. arg (
Arg ::with_name ( " unspendable " )
. long ( " unspendable " )
. value_name ( " TXID:VOUT " )
. help ( " Marks an utxo as unspendable, in case more inputs are needed to cover the extra fees " )
. takes_value ( true )
. number_of_values ( 1 )
. multiple ( true )
. validator ( outpoint_validator ) ,
)
. arg (
Arg ::with_name ( " fee_rate " )
. required ( true )
. short ( " fee " )
. long ( " fee_rate " )
. value_name ( " SATS_VBYTE " )
. help ( " The new targeted fee rate in sat/vbyte " )
. takes_value ( true ) ,
) ,
)
2020-05-08 23:30:45 +02:00
. subcommand (
SubCommand ::with_name ( " policies " )
. about ( " Returns the available spending policies for the descriptor " )
)
2020-05-10 17:42:02 +02:00
. subcommand (
SubCommand ::with_name ( " public_descriptor " )
. about ( " Returns the public version of the wallet's descriptor(s) " )
)
2020-05-08 23:30:45 +02:00
. subcommand (
SubCommand ::with_name ( " sign " )
. about ( " Signs and tries to finalize a PSBT " )
. arg (
Arg ::with_name ( " psbt " )
. long ( " psbt " )
. value_name ( " BASE64_PSBT " )
. help ( " Sets the PSBT to sign " )
. takes_value ( true )
. number_of_values ( 1 )
. required ( true ) ,
)
. arg (
Arg ::with_name ( " assume_height " )
. long ( " assume_height " )
. value_name ( " HEIGHT " )
. help ( " Assume the blockchain has reached a specific height. This affects the transaction finalization, if there are timelocks in the descriptor " )
. takes_value ( true )
. number_of_values ( 1 )
. required ( false ) ,
) )
. subcommand (
SubCommand ::with_name ( " broadcast " )
2020-05-17 18:01:52 +02:00
. about ( " Broadcasts a transaction to the network. Takes either a raw transaction or a PSBT to extract " )
2020-05-08 23:30:45 +02:00
. arg (
Arg ::with_name ( " psbt " )
. long ( " psbt " )
. value_name ( " BASE64_PSBT " )
2020-05-17 18:01:52 +02:00
. help ( " Sets the PSBT to extract and broadcast " )
. takes_value ( true )
. required_unless ( " tx " )
. number_of_values ( 1 ) )
. arg (
Arg ::with_name ( " tx " )
. long ( " tx " )
. value_name ( " RAWTX " )
. help ( " Sets the raw transaction to broadcast " )
. takes_value ( true )
. required_unless ( " psbt " )
. number_of_values ( 1 ) )
)
. subcommand (
SubCommand ::with_name ( " extract_psbt " )
. about ( " Extracts a raw transaction from a PSBT " )
. arg (
Arg ::with_name ( " psbt " )
. long ( " psbt " )
. value_name ( " BASE64_PSBT " )
. help ( " Sets the PSBT to extract " )
. takes_value ( true )
. required ( true )
. number_of_values ( 1 ) )
)
. subcommand (
SubCommand ::with_name ( " finalize_psbt " )
. about ( " Finalizes a psbt " )
. arg (
Arg ::with_name ( " psbt " )
. long ( " psbt " )
. value_name ( " BASE64_PSBT " )
. help ( " Sets the PSBT to finalize " )
. takes_value ( true )
. required ( true )
. number_of_values ( 1 ) )
. arg (
Arg ::with_name ( " assume_height " )
. long ( " assume_height " )
. value_name ( " HEIGHT " )
. help ( " Assume the blockchain has reached a specific height " )
2020-05-08 23:30:45 +02:00
. takes_value ( true )
. number_of_values ( 1 )
2020-05-17 18:01:52 +02:00
. required ( false ) )
)
. subcommand (
SubCommand ::with_name ( " combine_psbt " )
. about ( " Combines multiple PSBTs into one " )
. arg (
Arg ::with_name ( " psbt " )
. long ( " psbt " )
. value_name ( " BASE64_PSBT " )
. help ( " Add one PSBT to comine. This option can be repeated multiple times, one for each PSBT " )
. takes_value ( true )
. number_of_values ( 1 )
. required ( true )
. multiple ( true ) )
)
2020-05-08 23:30:45 +02:00
}
pub fn add_global_flags < ' a , ' b > ( app : App < ' a , ' b > ) -> App < ' a , ' b > {
app . arg (
Arg ::with_name ( " network " )
. short ( " n " )
. long ( " network " )
. value_name ( " NETWORK " )
. help ( " Sets the network " )
. takes_value ( true )
. default_value ( " testnet " )
. possible_values ( & [ " testnet " , " regtest " ] ) ,
)
. arg (
Arg ::with_name ( " wallet " )
. short ( " w " )
. long ( " wallet " )
. value_name ( " WALLET_NAME " )
. help ( " Selects the wallet to use " )
. takes_value ( true )
. default_value ( " main " ) ,
)
. arg (
Arg ::with_name ( " server " )
. short ( " s " )
. long ( " server " )
. value_name ( " SERVER:PORT " )
. help ( " Sets the Electrum server to use " )
. takes_value ( true )
2020-07-19 19:24:05 +02:00
. default_value ( " ssl://electrum.blockstream.info:60002 " ) ,
2020-05-08 23:30:45 +02:00
)
2020-07-15 18:49:24 +02:00
. arg (
Arg ::with_name ( " proxy " )
. short ( " p " )
. long ( " proxy " )
. value_name ( " SERVER:PORT " )
. help ( " Sets the SOCKS5 proxy for the Electrum client " )
. takes_value ( true ) ,
)
2020-05-08 23:30:45 +02:00
. arg (
Arg ::with_name ( " descriptor " )
. short ( " d " )
. long ( " descriptor " )
. value_name ( " DESCRIPTOR " )
. help ( " Sets the descriptor to use for the external addresses " )
. required ( true )
. takes_value ( true ) ,
)
. arg (
Arg ::with_name ( " change_descriptor " )
. short ( " c " )
. long ( " change_descriptor " )
. value_name ( " DESCRIPTOR " )
. help ( " Sets the descriptor to use for internal addresses " )
. takes_value ( true ) ,
)
. arg (
Arg ::with_name ( " v " )
. short ( " v " )
. multiple ( true )
. help ( " Sets the level of verbosity " ) ,
)
. subcommand ( SubCommand ::with_name ( " repl " ) . about ( " Opens an interactive shell " ) )
}
2020-07-20 15:51:57 +02:00
#[ maybe_async ]
2020-07-15 18:49:24 +02:00
pub fn handle_matches < C , D > (
2020-05-08 23:30:45 +02:00
wallet : & Wallet < C , D > ,
matches : ArgMatches < '_ > ,
2020-08-15 20:16:34 +02:00
) -> Result < serde_json ::Value , Error >
2020-05-08 23:30:45 +02:00
where
2020-09-09 18:17:49 +02:00
C : crate ::blockchain ::Blockchain ,
2020-05-08 23:30:45 +02:00
D : crate ::database ::BatchDatabase ,
{
if let Some ( _sub_matches ) = matches . subcommand_matches ( " get_new_address " ) {
2020-08-15 20:16:34 +02:00
Ok ( json! ( {
" address " : wallet . get_new_address ( ) ?
} ) )
2020-10-30 14:43:36 +01:00
} else if let Some ( sub_matches ) = matches . subcommand_matches ( " sync " ) {
let max_addresses : Option < u32 > = sub_matches
. value_of ( " max_addresses " )
. and_then ( | m | m . parse ( ) . ok ( ) ) ;
maybe_await! ( wallet . sync ( log_progress ( ) , max_addresses ) ) ? ;
2020-08-15 20:16:34 +02:00
Ok ( json! ( { } ) )
2020-05-08 23:30:45 +02:00
} else if let Some ( _sub_matches ) = matches . subcommand_matches ( " list_unspent " ) {
2020-08-15 20:16:34 +02:00
Ok ( serde_json ::to_value ( & wallet . list_unspent ( ) ? ) ? )
2020-07-21 18:37:15 +02:00
} else if let Some ( _sub_matches ) = matches . subcommand_matches ( " list_transactions " ) {
2020-08-15 20:16:34 +02:00
Ok ( serde_json ::to_value ( & wallet . list_transactions ( false ) ? ) ? )
2020-05-08 23:30:45 +02:00
} else if let Some ( _sub_matches ) = matches . subcommand_matches ( " get_balance " ) {
2020-08-15 20:16:34 +02:00
Ok ( json! ( {
" satoshi " : wallet . get_balance ( ) ?
} ) )
2020-05-08 23:30:45 +02:00
} else if let Some ( sub_matches ) = matches . subcommand_matches ( " create_tx " ) {
2020-08-31 10:49:44 +02:00
let recipients = sub_matches
2020-05-08 23:30:45 +02:00
. values_of ( " to " )
. unwrap ( )
2020-08-31 10:49:44 +02:00
. map ( | s | parse_recipient ( s ) )
2020-05-17 18:01:52 +02:00
. collect ::< Result < Vec < _ > , _ > > ( )
2020-10-28 15:34:46 -07:00
. map_err ( Error ::Generic ) ? ;
2020-08-31 10:49:44 +02:00
let mut tx_builder = TxBuilder ::with_recipients ( recipients ) ;
2020-08-08 12:06:40 +02:00
if sub_matches . is_present ( " send_all " ) {
tx_builder = tx_builder . send_all ( ) ;
}
2020-08-13 16:51:27 +02:00
if sub_matches . is_present ( " enable_rbf " ) {
tx_builder = tx_builder . enable_rbf ( ) ;
}
2020-05-08 23:30:45 +02:00
2020-08-06 13:09:39 +02:00
if let Some ( fee_rate ) = sub_matches . value_of ( " fee_rate " ) {
let fee_rate = f32 ::from_str ( fee_rate ) . map_err ( | s | Error ::Generic ( s . to_string ( ) ) ) ? ;
2020-08-07 11:23:01 +02:00
tx_builder = tx_builder . fee_rate ( FeeRate ::from_sat_per_vb ( fee_rate ) ) ;
2020-08-06 13:09:39 +02:00
}
if let Some ( utxos ) = sub_matches . values_of ( " utxos " ) {
let utxos = utxos
. map ( | i | parse_outpoint ( i ) )
. collect ::< Result < Vec < _ > , _ > > ( )
2020-10-28 15:34:46 -07:00
. map_err ( Error ::Generic ) ? ;
2020-10-22 12:07:51 +11:00
tx_builder = tx_builder . utxos ( utxos ) . manually_selected_only ( ) ;
2020-08-06 13:09:39 +02:00
}
if let Some ( unspendable ) = sub_matches . values_of ( " unspendable " ) {
let unspendable = unspendable
. map ( | i | parse_outpoint ( i ) )
. collect ::< Result < Vec < _ > , _ > > ( )
2020-10-28 15:34:46 -07:00
. map_err ( Error ::Generic ) ? ;
2020-08-06 16:56:41 +02:00
tx_builder = tx_builder . unspendable ( unspendable ) ;
2020-08-06 13:09:39 +02:00
}
if let Some ( policy ) = sub_matches . value_of ( " policy " ) {
let policy = serde_json ::from_str ::< BTreeMap < String , Vec < usize > > > ( & policy )
. map_err ( | s | Error ::Generic ( s . to_string ( ) ) ) ? ;
2020-08-06 16:56:41 +02:00
tx_builder = tx_builder . policy_path ( policy ) ;
2020-08-06 13:09:39 +02:00
}
2020-08-15 20:16:34 +02:00
let ( psbt , details ) = wallet . create_tx ( tx_builder ) ? ;
Ok ( json! ( {
" psbt " : base64 ::encode ( & serialize ( & psbt ) ) ,
" details " : details ,
} ) )
2020-08-13 16:51:27 +02:00
} else if let Some ( sub_matches ) = matches . subcommand_matches ( " bump_fee " ) {
let txid = Txid ::from_str ( sub_matches . value_of ( " txid " ) . unwrap ( ) )
. map_err ( | s | Error ::Generic ( s . to_string ( ) ) ) ? ;
let fee_rate = f32 ::from_str ( sub_matches . value_of ( " fee_rate " ) . unwrap ( ) )
. map_err ( | s | Error ::Generic ( s . to_string ( ) ) ) ? ;
let mut tx_builder = TxBuilder ::new ( ) . fee_rate ( FeeRate ::from_sat_per_vb ( fee_rate ) ) ;
if sub_matches . is_present ( " send_all " ) {
tx_builder = tx_builder . send_all ( ) ;
}
if let Some ( utxos ) = sub_matches . values_of ( " utxos " ) {
let utxos = utxos
. map ( | i | parse_outpoint ( i ) )
. collect ::< Result < Vec < _ > , _ > > ( )
2020-10-28 15:34:46 -07:00
. map_err ( Error ::Generic ) ? ;
2020-08-13 16:51:27 +02:00
tx_builder = tx_builder . utxos ( utxos ) ;
}
if let Some ( unspendable ) = sub_matches . values_of ( " unspendable " ) {
let unspendable = unspendable
. map ( | i | parse_outpoint ( i ) )
. collect ::< Result < Vec < _ > , _ > > ( )
2020-10-28 15:34:46 -07:00
. map_err ( Error ::Generic ) ? ;
2020-08-13 16:51:27 +02:00
tx_builder = tx_builder . unspendable ( unspendable ) ;
}
2020-08-15 20:16:34 +02:00
let ( psbt , details ) = wallet . bump_fee ( & txid , tx_builder ) ? ;
Ok ( json! ( {
" psbt " : base64 ::encode ( & serialize ( & psbt ) ) ,
" details " : details ,
} ) )
2020-05-08 23:30:45 +02:00
} else if let Some ( _sub_matches ) = matches . subcommand_matches ( " policies " ) {
2020-08-15 20:16:34 +02:00
Ok ( json! ( {
" external " : wallet . policies ( ScriptType ::External ) ? ,
" internal " : wallet . policies ( ScriptType ::Internal ) ? ,
} ) )
2020-05-10 17:42:02 +02:00
} else if let Some ( _sub_matches ) = matches . subcommand_matches ( " public_descriptor " ) {
2020-08-15 20:16:34 +02:00
Ok ( json! ( {
" external " : wallet . public_descriptor ( ScriptType ::External ) ? . map ( | d | d . to_string ( ) ) ,
" internal " : wallet . public_descriptor ( ScriptType ::Internal ) ? . map ( | d | d . to_string ( ) ) ,
} ) )
2020-05-08 23:30:45 +02:00
} else if let Some ( sub_matches ) = matches . subcommand_matches ( " sign " ) {
let psbt = base64 ::decode ( sub_matches . value_of ( " psbt " ) . unwrap ( ) ) . unwrap ( ) ;
let psbt : PartiallySignedTransaction = deserialize ( & psbt ) . unwrap ( ) ;
let assume_height = sub_matches
. value_of ( " assume_height " )
2020-10-28 15:34:46 -07:00
. map ( | s | s . parse ( ) . unwrap ( ) ) ;
2020-05-08 23:30:45 +02:00
let ( psbt , finalized ) = wallet . sign ( psbt , assume_height ) ? ;
2020-08-15 20:16:34 +02:00
Ok ( json! ( {
" psbt " : base64 ::encode ( & serialize ( & psbt ) ) ,
" is_finalized " : finalized ,
} ) )
2020-05-08 23:30:45 +02:00
} else if let Some ( sub_matches ) = matches . subcommand_matches ( " broadcast " ) {
2020-05-17 18:01:52 +02:00
let tx = if sub_matches . value_of ( " psbt " ) . is_some ( ) {
let psbt = base64 ::decode ( & sub_matches . value_of ( " psbt " ) . unwrap ( ) ) . unwrap ( ) ;
let psbt : PartiallySignedTransaction = deserialize ( & psbt ) . unwrap ( ) ;
psbt . extract_tx ( )
} else if sub_matches . value_of ( " tx " ) . is_some ( ) {
deserialize ( & Vec ::< u8 > ::from_hex ( & sub_matches . value_of ( " tx " ) . unwrap ( ) ) . unwrap ( ) )
. unwrap ( )
} else {
panic! ( " Missing `psbt` and `tx` option " ) ;
} ;
2020-07-20 15:51:57 +02:00
let txid = maybe_await! ( wallet . broadcast ( tx ) ) ? ;
2020-08-15 20:16:34 +02:00
Ok ( json! ( { " txid " : txid } ) )
2020-05-17 18:01:52 +02:00
} else if let Some ( sub_matches ) = matches . subcommand_matches ( " extract_psbt " ) {
let psbt = base64 ::decode ( & sub_matches . value_of ( " psbt " ) . unwrap ( ) ) . unwrap ( ) ;
let psbt : PartiallySignedTransaction = deserialize ( & psbt ) . unwrap ( ) ;
2020-08-15 20:16:34 +02:00
Ok ( json! ( {
" raw_tx " : serialize_hex ( & psbt . extract_tx ( ) ) ,
} ) )
2020-05-17 18:01:52 +02:00
} else if let Some ( sub_matches ) = matches . subcommand_matches ( " finalize_psbt " ) {
let psbt = base64 ::decode ( & sub_matches . value_of ( " psbt " ) . unwrap ( ) ) . unwrap ( ) ;
2020-08-31 10:49:44 +02:00
let psbt : PartiallySignedTransaction = deserialize ( & psbt ) . unwrap ( ) ;
2020-05-17 18:01:52 +02:00
let assume_height = sub_matches
. value_of ( " assume_height " )
2020-10-28 15:34:46 -07:00
. map ( | s | s . parse ( ) . unwrap ( ) ) ;
2020-05-17 18:01:52 +02:00
2020-08-31 10:49:44 +02:00
let ( psbt , finalized ) = wallet . finalize_psbt ( psbt , assume_height ) ? ;
2020-08-15 20:16:34 +02:00
Ok ( json! ( {
" psbt " : base64 ::encode ( & serialize ( & psbt ) ) ,
" is_finalized " : finalized ,
} ) )
2020-05-17 18:01:52 +02:00
} else if let Some ( sub_matches ) = matches . subcommand_matches ( " combine_psbt " ) {
let mut psbts = sub_matches
. values_of ( " psbt " )
. unwrap ( )
. map ( | s | {
let psbt = base64 ::decode ( & s ) . unwrap ( ) ;
let psbt : PartiallySignedTransaction = deserialize ( & psbt ) . unwrap ( ) ;
psbt
} )
. collect ::< Vec < _ > > ( ) ;
let init_psbt = psbts . pop ( ) . unwrap ( ) ;
let final_psbt = psbts
. into_iter ( )
. try_fold ::< _ , _ , Result < PartiallySignedTransaction , Error > > (
init_psbt ,
| mut acc , x | {
acc . merge ( x ) ? ;
Ok ( acc )
} ,
) ? ;
2020-08-15 20:16:34 +02:00
Ok ( json! ( { " psbt " : base64 ::encode ( & serialize ( & final_psbt ) ) } ) )
2020-05-08 23:30:45 +02:00
} else {
2020-08-15 20:16:34 +02:00
Ok ( serde_json ::Value ::Null )
2020-05-08 23:30:45 +02:00
}
}