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,13 @@ 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 } ;
31+ use serde:: de:: DeserializeOwned ;
3032
3133use crate :: api:: AddressStats ;
3234use crate :: {
33- BlockStatus , BlockSummary , Builder , Error , MerkleProof , OutputStatus , Tx , TxStatus ,
34- BASE_BACKOFF_MILLIS , RETRYABLE_ERROR_CODES ,
35+ BlockStatus , BlockSummary , Builder , Error , MerkleProof , OutputStatus , SubmitPackageResult , Tx ,
36+ TxStatus , BASE_BACKOFF_MILLIS , RETRYABLE_ERROR_CODES ,
3537} ;
3638
3739#[ derive( Debug , Clone ) ]
@@ -249,21 +251,27 @@ impl<S: Sleeper> AsyncClient<S> {
249251 }
250252 }
251253
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`].
254+ /// Make an HTTP POST request to given URL, converting any `T` that
255+ /// implement [`Into<Body>`] and setting query parameters, if any.
257256 ///
258257 /// # Errors
259258 ///
260259 /// 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 ( ) ;
260+ /// response's [`serde_json`] deserialization.
261+ async fn post_request_bytes < ' de , T : Into < Body > , R : DeserializeOwned > (
262+ & self ,
263+ path : & str ,
264+ body : T ,
265+ query_params : Option < HashSet < ( & str , String ) > >
266+ ) -> Result < R , Error > {
267+ let url: String = format ! ( "{}{}" , self . url, path) ;
268+ let mut request = self . client . post ( url) . body ( body) ;
269+
270+ for param in query_params. unwrap_or_default ( ) {
271+ request = request. query ( & param) ;
272+ }
265273
266- let response = self . client . post ( url ) . body ( body ) . send ( ) . await ?;
274+ let response = request . send ( ) . await ?;
267275
268276 if !response. status ( ) . is_success ( ) {
269277 return Err ( Error :: HttpResponse {
@@ -272,7 +280,10 @@ impl<S: Sleeper> AsyncClient<S> {
272280 } ) ;
273281 }
274282
275- Ok ( ( ) )
283+ response
284+ . json :: < R > ( )
285+ . await
286+ . map_err ( Error :: Reqwest )
276287 }
277288
278289 /// Get a [`Transaction`] option given its [`Txid`]
@@ -360,7 +371,41 @@ impl<S: Sleeper> AsyncClient<S> {
360371
361372 /// Broadcast a [`Transaction`] to Esplora
362373 pub async fn broadcast ( & self , transaction : & Transaction ) -> Result < ( ) , Error > {
363- self . post_request_hex ( "/tx" , transaction) . await
374+ let body = serialize :: < Transaction > ( & transaction) . to_lower_hex_string ( ) ;
375+ self . post_request_bytes ( "/tx" , body, None ) . await
376+ }
377+
378+ /// Broadcast a package of [`Transaction`] to Esplora
379+ ///
380+ /// if `maxfeerate` is provided, any transaction whose
381+ /// fee is higher will be rejected
382+ ///
383+ /// if `maxburnamount` is provided, any transaction
384+ /// with higher provably unspendable outputs amount
385+ /// will be rejected
386+ pub async fn submit_package (
387+ & self ,
388+ transactions : & [ Transaction ] ,
389+ maxfeerate : Option < f64 > ,
390+ maxburnamount : Option < f64 > ,
391+ ) -> Result < SubmitPackageResult , Error > {
392+ let mut queryparams = HashSet :: < ( & str , String ) > :: new ( ) ;
393+ if let Some ( maxfeerate) = maxfeerate {
394+ queryparams. insert ( ( "maxfeerate" , maxfeerate. to_string ( ) ) ) ;
395+ }
396+ if let Some ( maxburnamount) = maxburnamount {
397+ queryparams. insert ( ( "maxburnamount" , maxburnamount. to_string ( ) ) ) ;
398+ }
399+
400+ let serialized_txs = transactions
401+ . iter ( )
402+ . map ( |tx| serialize_hex ( & tx) )
403+ . collect :: < Vec < _ > > ( ) ;
404+
405+ self . post_request_bytes :: < _ , SubmitPackageResult > (
406+ "/txs/package" ,
407+ serde_json:: to_string ( & serialized_txs) . unwrap ( ) ,
408+ Some ( queryparams) ) . await
364409 }
365410
366411 /// Get the current height of the blockchain tip
0 commit comments