11
11
12
12
//! Esplora by way of `reqwest` HTTP client.
13
13
14
- use std:: collections:: HashMap ;
14
+ use std:: collections:: { HashMap , HashSet } ;
15
15
use std:: marker:: PhantomData ;
16
16
use std:: str:: FromStr ;
17
17
18
- use bitcoin:: consensus:: { deserialize, serialize, Decodable , Encodable } ;
18
+ use bitcoin:: consensus:: encode:: serialize_hex;
19
+ use bitcoin:: consensus:: { deserialize, serialize, Decodable } ;
19
20
use bitcoin:: hashes:: { sha256, Hash } ;
20
21
use bitcoin:: hex:: { DisplayHex , FromHex } ;
21
22
use bitcoin:: Address ;
@@ -26,12 +27,12 @@ use bitcoin::{
26
27
#[ allow( unused_imports) ]
27
28
use log:: { debug, error, info, trace} ;
28
29
29
- use reqwest:: { header, Client , Response } ;
30
+ use reqwest:: { header, Body , Client , Response } ;
30
31
31
32
use crate :: api:: AddressStats ;
32
33
use crate :: {
33
- BlockStatus , BlockSummary , Builder , Error , MerkleProof , OutputStatus , Tx , TxStatus ,
34
- BASE_BACKOFF_MILLIS , RETRYABLE_ERROR_CODES ,
34
+ BlockStatus , BlockSummary , Builder , Error , MerkleProof , OutputStatus , SubmitPackageResult , Tx ,
35
+ TxStatus , BASE_BACKOFF_MILLIS , RETRYABLE_ERROR_CODES ,
35
36
} ;
36
37
37
38
#[ derive( Debug , Clone ) ]
@@ -249,21 +250,27 @@ impl<S: Sleeper> AsyncClient<S> {
249
250
}
250
251
}
251
252
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.
257
255
///
258
256
/// # Errors
259
257
///
260
258
/// 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
+ }
265
272
266
- let response = self . client . post ( url ) . body ( body ) . send ( ) . await ?;
273
+ let response = request . send ( ) . await ?;
267
274
268
275
if !response. status ( ) . is_success ( ) {
269
276
return Err ( Error :: HttpResponse {
@@ -272,7 +279,7 @@ impl<S: Sleeper> AsyncClient<S> {
272
279
} ) ;
273
280
}
274
281
275
- Ok ( ( ) )
282
+ Ok ( response )
276
283
}
277
284
278
285
/// Get a [`Transaction`] option given its [`Txid`]
@@ -359,8 +366,49 @@ impl<S: Sleeper> AsyncClient<S> {
359
366
}
360
367
361
368
/// 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 ?)
364
412
}
365
413
366
414
/// Get the current height of the blockchain tip
0 commit comments