Skip to main content

bee/debug/
transactions.rs

1//! Transaction lifecycle endpoints — pending list, get, rebroadcast,
2//! cancel. Mirrors bee-go's `pkg/debug/transactions.go`.
3
4use num_bigint::BigInt;
5use reqwest::Method;
6use serde::{Deserialize, Deserializer};
7
8use crate::client::request;
9use crate::swarm::Error;
10
11use super::DebugApi;
12
13/// One pending transaction Bee knows about. Mirrors bee-go
14/// `TransactionInfo`. The bigint fields arrive as decimal strings on
15/// the wire; missing/empty values become `None`.
16#[derive(Clone, Debug, PartialEq, Eq, Deserialize)]
17#[serde(rename_all = "camelCase")]
18pub struct TransactionInfo {
19    /// Transaction hash (hex).
20    pub transaction_hash: String,
21    /// Recipient address (hex).
22    pub to: String,
23    /// Sender nonce.
24    pub nonce: u64,
25    /// Gas price (PLUR).
26    #[serde(default, deserialize_with = "deserialize_opt_bigint")]
27    pub gas_price: Option<BigInt>,
28    /// Gas limit.
29    pub gas_limit: u64,
30    /// Tip boost.
31    #[serde(default)]
32    pub gas_tip_boost: i64,
33    /// EIP-1559 tip cap (PLUR).
34    #[serde(default, deserialize_with = "deserialize_opt_bigint")]
35    pub gas_tip_cap: Option<BigInt>,
36    /// EIP-1559 fee cap (PLUR).
37    #[serde(default, deserialize_with = "deserialize_opt_bigint")]
38    pub gas_fee_cap: Option<BigInt>,
39    /// Calldata (hex).
40    #[serde(default)]
41    pub data: String,
42    /// Creation time (RFC 3339 string).
43    #[serde(default)]
44    pub created: String,
45    /// Operator-supplied description.
46    #[serde(default)]
47    pub description: String,
48    /// Transaction value (PLUR).
49    #[serde(default, deserialize_with = "deserialize_opt_bigint")]
50    pub value: Option<BigInt>,
51}
52
53fn deserialize_opt_bigint<'de, D>(d: D) -> Result<Option<BigInt>, D::Error>
54where
55    D: Deserializer<'de>,
56{
57    let s: String = Deserialize::deserialize(d)?;
58    if s.is_empty() {
59        return Ok(None);
60    }
61    s.parse::<BigInt>()
62        .map(Some)
63        .map_err(serde::de::Error::custom)
64}
65
66impl DebugApi {
67    /// `GET /transactions` — every pending transaction Bee tracks.
68    pub async fn pending_transactions(&self) -> Result<Vec<TransactionInfo>, Error> {
69        let builder = request(&self.inner, Method::GET, "transactions")?;
70        #[derive(Deserialize)]
71        struct Resp {
72            #[serde(rename = "pendingTransactions")]
73            pending_transactions: Vec<TransactionInfo>,
74        }
75        let r: Resp = self.inner.send_json(builder).await?;
76        Ok(r.pending_transactions)
77    }
78
79    /// `GET /transactions/{hash}` — info for one pending transaction.
80    pub async fn pending_transaction(&self, tx_hash: &str) -> Result<TransactionInfo, Error> {
81        let path = format!("transactions/{tx_hash}");
82        let builder = request(&self.inner, Method::GET, &path)?;
83        self.inner.send_json(builder).await
84    }
85
86    /// `POST /transactions/{hash}` — replay a pending transaction to
87    /// the network. Returns the resulting transaction hash.
88    pub async fn rebroadcast_transaction(&self, tx_hash: &str) -> Result<String, Error> {
89        let path = format!("transactions/{tx_hash}");
90        let builder = request(&self.inner, Method::POST, &path)?;
91        #[derive(Deserialize)]
92        struct Resp {
93            #[serde(rename = "transactionHash")]
94            transaction_hash: String,
95        }
96        let r: Resp = self.inner.send_json(builder).await?;
97        Ok(r.transaction_hash)
98    }
99
100    /// `DELETE /transactions/{hash}` — cancel a pending transaction by
101    /// replacing it at the same nonce. `gas_price` is optional (`None`
102    /// lets Bee pick); when `Some(_)` it's sent in the `gas-price`
103    /// header so the replacement bumps the fee. Returns the resulting
104    /// transaction hash.
105    pub async fn cancel_transaction(
106        &self,
107        tx_hash: &str,
108        gas_price: Option<&BigInt>,
109    ) -> Result<String, Error> {
110        let path = format!("transactions/{tx_hash}");
111        let mut builder = request(&self.inner, Method::DELETE, &path)?;
112        if let Some(gp) = gas_price {
113            builder = builder.header("gas-price", gp.to_string());
114        }
115        #[derive(Deserialize)]
116        struct Resp {
117            #[serde(rename = "transactionHash")]
118            transaction_hash: String,
119        }
120        let r: Resp = self.inner.send_json(builder).await?;
121        Ok(r.transaction_hash)
122    }
123}