1111
1212//! Esplora by way of `reqwest` HTTP client. 
1313
14- use  std:: collections:: HashMap ; 
14+ use  std:: collections:: { HashMap ,   HashSet } ; 
1515use  std:: marker:: PhantomData ; 
1616use  std:: str:: FromStr ; 
1717
18- use  bitcoin:: consensus:: { deserialize,  serialize,  Decodable ,  Encodable } ; 
18+ use  bitcoin:: consensus:: encode:: serialize_hex; 
19+ use  bitcoin:: consensus:: { deserialize,  serialize,  Decodable } ; 
1920use  bitcoin:: hashes:: { sha256,  Hash } ; 
2021use  bitcoin:: hex:: { DisplayHex ,  FromHex } ; 
2122use  bitcoin:: Address ; 
@@ -26,12 +27,12 @@ use bitcoin::{
2627#[ allow( unused_imports) ]  
2728use  log:: { debug,  error,  info,  trace} ; 
2829
29- use  reqwest:: { header,  Client ,  Response } ; 
30+ use  reqwest:: { header,  Body ,   Client ,  Response } ; 
3031
3132use  crate :: api:: AddressStats ; 
3233use  crate :: { 
33-     BlockStatus ,  BlockSummary ,  Builder ,  Error ,  MerkleProof ,  OutputStatus ,  Tx ,   TxStatus ,   Utxo , 
34-     BASE_BACKOFF_MILLIS ,  RETRYABLE_ERROR_CODES , 
34+     BlockStatus ,  BlockSummary ,  Builder ,  Error ,  MerkleProof ,  OutputStatus ,  SubmitPackageResult ,   Tx , 
35+     TxStatus ,   BASE_BACKOFF_MILLIS ,  RETRYABLE_ERROR_CODES ,   Utxo , 
3536} ; 
3637
3738#[ derive( Debug ,  Clone ) ]  
@@ -249,21 +250,27 @@ impl<S: Sleeper> AsyncClient<S> {
249250        } 
250251    } 
251252
252-     /// Make an HTTP POST request to given URL, serializing from any `T` that 
253- /// implement [`bitcoin::consensus::Encodable`]. 
254- /// 
255- /// It should be used when requesting Esplora endpoints that expected a 
256- /// native bitcoin type serialized with [`bitcoin::consensus::Encodable`]. 
253+     /// Make an HTTP POST request to given URL, converting any `T` that 
254+ /// implement [`Into<Body>`] and setting query parameters, if any. 
257255/// 
258256/// # Errors 
259257/// 
260258/// This function will return an error either from the HTTP client, or the 
261- /// [`bitcoin::consensus::Encodable`] serialization. 
262- async  fn  post_request_hex < T :  Encodable > ( & self ,  path :  & str ,  body :  T )  -> Result < ( ) ,  Error >  { 
263-         let  url = format ! ( "{}{}" ,  self . url,  path) ; 
264-         let  body = serialize :: < T > ( & body) . to_lower_hex_string ( ) ; 
259+ /// response's [`serde_json`] deserialization. 
260+ async  fn  post_request_bytes < T :  Into < Body > > ( 
261+         & self , 
262+         path :  & str , 
263+         body :  T , 
264+         query_params :  Option < HashSet < ( & str ,  String ) > > , 
265+     )  -> Result < Response ,  Error >  { 
266+         let  url:  String  = format ! ( "{}{}" ,  self . url,  path) ; 
267+         let  mut  request = self . client . post ( url) . body ( body) ; 
268+ 
269+         for  param in  query_params. unwrap_or_default ( )  { 
270+             request = request. query ( & param) ; 
271+         } 
265272
266-         let  response = self . client . post ( url ) . body ( body ) . send ( ) . await ?; 
273+         let  response = request . send ( ) . await ?; 
267274
268275        if  !response. status ( ) . is_success ( )  { 
269276            return  Err ( Error :: HttpResponse  { 
@@ -272,7 +279,7 @@ impl<S: Sleeper> AsyncClient<S> {
272279            } ) ; 
273280        } 
274281
275-         Ok ( ( ) ) 
282+         Ok ( response ) 
276283    } 
277284
278285    /// Get a [`Transaction`] option given its [`Txid`] 
@@ -359,8 +366,49 @@ impl<S: Sleeper> AsyncClient<S> {
359366    } 
360367
361368    /// Broadcast a [`Transaction`] to Esplora 
362- pub  async  fn  broadcast ( & self ,  transaction :  & Transaction )  -> Result < ( ) ,  Error >  { 
363-         self . post_request_hex ( "/tx" ,  transaction) . await 
369+ pub  async  fn  broadcast ( & self ,  transaction :  & Transaction )  -> Result < Txid ,  Error >  { 
370+         let  body = serialize :: < Transaction > ( transaction) . to_lower_hex_string ( ) ; 
371+         let  response = self . post_request_bytes ( "/tx" ,  body,  None ) . await ?; 
372+         let  txid = Txid :: from_str ( & response. text ( ) . await ?) . map_err ( |_| Error :: InvalidResponse ) ?; 
373+         Ok ( txid) 
374+     } 
375+ 
376+     /// Broadcast a package of [`Transaction`] to Esplora 
377+ /// 
378+ /// if `maxfeerate` is provided, any transaction whose 
379+ /// fee is higher will be rejected 
380+ /// 
381+ /// if  `maxburnamount` is provided, any transaction 
382+ /// with higher provably unspendable outputs amount 
383+ /// will be rejected 
384+ pub  async  fn  submit_package ( 
385+         & self , 
386+         transactions :  & [ Transaction ] , 
387+         maxfeerate :  Option < f64 > , 
388+         maxburnamount :  Option < f64 > , 
389+     )  -> Result < SubmitPackageResult ,  Error >  { 
390+         let  mut  queryparams = HashSet :: < ( & str ,  String ) > :: new ( ) ; 
391+         if  let  Some ( maxfeerate)  = maxfeerate { 
392+             queryparams. insert ( ( "maxfeerate" ,  maxfeerate. to_string ( ) ) ) ; 
393+         } 
394+         if  let  Some ( maxburnamount)  = maxburnamount { 
395+             queryparams. insert ( ( "maxburnamount" ,  maxburnamount. to_string ( ) ) ) ; 
396+         } 
397+ 
398+         let  serialized_txs = transactions
399+             . iter ( ) 
400+             . map ( |tx| serialize_hex ( & tx) ) 
401+             . collect :: < Vec < _ > > ( ) ; 
402+ 
403+         let  response = self 
404+             . post_request_bytes ( 
405+                 "/txs/package" , 
406+                 serde_json:: to_string ( & serialized_txs) . unwrap ( ) , 
407+                 Some ( queryparams) , 
408+             ) 
409+             . await ?; 
410+ 
411+         Ok ( response. json :: < SubmitPackageResult > ( ) . await ?) 
364412    } 
365413
366414    /// Get the current height of the blockchain tip 
0 commit comments