Skip to main content

bee/postage/
endpoints.rs

1//! `/stamps` and `/batches` endpoint methods.
2
3use num_bigint::BigInt;
4use reqwest::Method;
5use serde::Deserialize;
6
7use crate::api::PostageBatchOptions;
8use crate::client::request;
9use crate::swarm::{BatchId, Error};
10
11use super::PostageApi;
12use super::types::{GlobalPostageBatch, PostageBatch, PostageBatchBuckets};
13
14#[derive(Deserialize)]
15struct StampsResp {
16    stamps: Vec<PostageBatch>,
17}
18
19#[derive(Deserialize)]
20struct BatchesResp {
21    batches: Vec<GlobalPostageBatch>,
22}
23
24#[derive(Deserialize)]
25struct BatchIdResp {
26    #[serde(rename = "batchID")]
27    batch_id: String,
28}
29
30impl PostageApi {
31    /// Every postage batch owned by this node — `GET /stamps`.
32    pub async fn get_postage_batches(&self) -> Result<Vec<PostageBatch>, Error> {
33        let builder = request(&self.inner, Method::GET, "stamps")?;
34        let res: StampsResp = self.inner.send_json(builder).await?;
35        Ok(res.stamps)
36    }
37
38    /// Single owned batch by id — `GET /stamps/{id}`.
39    pub async fn get_postage_batch(&self, batch_id: &BatchId) -> Result<PostageBatch, Error> {
40        let path = format!("stamps/{}", batch_id.to_hex());
41        let builder = request(&self.inner, Method::GET, &path)?;
42        self.inner.send_json(builder).await
43    }
44
45    /// Per-bucket collision stats — `GET /stamps/{id}/buckets`.
46    pub async fn get_postage_batch_buckets(
47        &self,
48        batch_id: &BatchId,
49    ) -> Result<PostageBatchBuckets, Error> {
50        let path = format!("stamps/{}/buckets", batch_id.to_hex());
51        let builder = request(&self.inner, Method::GET, &path)?;
52        self.inner.send_json(builder).await
53    }
54
55    /// Every chain-visible postage batch — `GET /batches`. Returns the
56    /// chain-wide view (no owner-only fields).
57    pub async fn get_global_postage_batches(&self) -> Result<Vec<GlobalPostageBatch>, Error> {
58        let builder = request(&self.inner, Method::GET, "batches")?;
59        let res: BatchesResp = self.inner.send_json(builder).await?;
60        Ok(res.batches)
61    }
62
63    /// Buy a new postage batch — `POST /stamps/{amount}/{depth}`.
64    /// Returns the freshly-minted [`BatchId`].
65    ///
66    /// `amount` is the per-chunk amount (PLUR) and `depth` is the
67    /// stamp depth — together they determine effective capacity and
68    /// TTL. For a `(size, duration)`-shaped API see
69    /// [`crate::storage::buy_storage`].
70    ///
71    /// # Examples
72    ///
73    /// ```no_run
74    /// use bee::Client;
75    /// use num_bigint::BigInt;
76    ///
77    /// # async fn run() -> Result<(), bee::Error> {
78    /// let client = Client::new("http://localhost:1633")?;
79    /// let amount = BigInt::from(414_720_000u64);
80    /// let batch_id = client
81    ///     .postage()
82    ///     .create_postage_batch(&amount, 22, Some("my-batch"))
83    ///     .await?;
84    /// println!("bought batch {}", batch_id.to_hex());
85    /// # Ok(()) }
86    /// ```
87    pub async fn create_postage_batch(
88        &self,
89        amount: &BigInt,
90        depth: u8,
91        label: Option<&str>,
92    ) -> Result<BatchId, Error> {
93        let opts = label.map(|l| PostageBatchOptions {
94            label: Some(l.to_string()),
95            ..Default::default()
96        });
97        self.create_postage_batch_with_options(amount, depth, opts.as_ref())
98            .await
99    }
100
101    /// Buy a new postage batch with full options support. Mirrors
102    /// bee-js `Bee.createPostageBatch` and bee-go's options-style
103    /// constructor — `label` and `immutable` map to query parameters,
104    /// `gas_price` / `gas_limit` map to `Gas-Price` / `Gas-Limit` headers.
105    pub async fn create_postage_batch_with_options(
106        &self,
107        amount: &BigInt,
108        depth: u8,
109        opts: Option<&PostageBatchOptions>,
110    ) -> Result<BatchId, Error> {
111        let path = format!("stamps/{amount}/{depth}");
112        let mut builder = request(&self.inner, Method::POST, &path)?;
113        if let Some(o) = opts {
114            let mut query: Vec<(&str, String)> = Vec::new();
115            if let Some(l) = &o.label {
116                query.push(("label", l.clone()));
117            }
118            if let Some(im) = o.immutable {
119                query.push(("immutable", im.to_string()));
120            }
121            if !query.is_empty() {
122                let q: Vec<(&str, &str)> = query.iter().map(|(k, v)| (*k, v.as_str())).collect();
123                builder = builder.query(&q);
124            }
125            if let Some(gp) = &o.gas_price {
126                builder = builder.header("Gas-Price", gp);
127            }
128            if let Some(gl) = &o.gas_limit {
129                builder = builder.header("Gas-Limit", gl);
130            }
131        }
132        let res: BatchIdResp = self.inner.send_json(builder).await?;
133        BatchId::from_hex(&res.batch_id)
134    }
135
136    /// Top up an existing batch — `PATCH /stamps/topup/{id}/{amount}`.
137    pub async fn top_up_batch(&self, batch_id: &BatchId, amount: &BigInt) -> Result<(), Error> {
138        let path = format!("stamps/topup/{}/{amount}", batch_id.to_hex());
139        let builder = request(&self.inner, Method::PATCH, &path)?;
140        self.inner.send(builder).await?;
141        Ok(())
142    }
143
144    /// Increase the depth of an existing batch — `PATCH /stamps/dilute/{id}/{depth}`.
145    pub async fn dilute_batch(&self, batch_id: &BatchId, depth: u8) -> Result<(), Error> {
146        let path = format!("stamps/dilute/{}/{depth}", batch_id.to_hex());
147        let builder = request(&self.inner, Method::PATCH, &path)?;
148        self.inner.send(builder).await?;
149        Ok(())
150    }
151}