Skip to main content

cdk_bdk/storage/
send.rs

1use std::str::FromStr;
2
3use uuid::Uuid;
4
5use super::{
6    BdkStorage, FailedSendAttemptRecord, FinalizedSendIntentRecord, BDK_NAMESPACE,
7    FINALIZED_INTENT_NAMESPACE, FINALIZED_SEND_INTENT_QUOTE_ID_NAMESPACE, SEND_INTENT_NAMESPACE,
8    SEND_INTENT_QUOTE_ID_NAMESPACE,
9};
10use crate::error::Error;
11use crate::send::batch_transaction::record::{SendBatchRecord, SendBatchState};
12use crate::send::payment_intent::record::{SendIntentRecord, SendIntentState};
13
14impl BdkStorage {
15    // ── Send Intent storage ──────────────────────────────────────────
16
17    /// Store a new send intent and quote-id index atomically.
18    pub async fn create_send_intent_if_absent(
19        &self,
20        intent: &SendIntentRecord,
21    ) -> Result<(), Error> {
22        let mut tx = self
23            .kv_store
24            .begin_transaction()
25            .await
26            .map_err(Error::from)?;
27
28        let active = tx
29            .kv_read(
30                BDK_NAMESPACE,
31                SEND_INTENT_QUOTE_ID_NAMESPACE,
32                &intent.quote_id,
33            )
34            .await
35            .map_err(Error::from)?;
36
37        if active.is_some() {
38            tx.rollback().await.map_err(Error::from)?;
39            return Err(Error::DuplicateQuoteId(intent.quote_id.clone()));
40        }
41
42        let finalized = tx
43            .kv_read(
44                BDK_NAMESPACE,
45                FINALIZED_SEND_INTENT_QUOTE_ID_NAMESPACE,
46                &intent.quote_id,
47            )
48            .await
49            .map_err(Error::from)?;
50
51        if finalized.is_some() {
52            tx.rollback().await.map_err(Error::from)?;
53            return Err(Error::DuplicateQuoteId(intent.quote_id.clone()));
54        }
55
56        let serialized = serde_json::to_vec(intent)?;
57        tx.kv_write(
58            BDK_NAMESPACE,
59            SEND_INTENT_NAMESPACE,
60            &intent.intent_id.to_string(),
61            &serialized,
62        )
63        .await
64        .map_err(Error::from)?;
65        tx.kv_write(
66            BDK_NAMESPACE,
67            SEND_INTENT_QUOTE_ID_NAMESPACE,
68            &intent.quote_id,
69            intent.intent_id.to_string().as_bytes(),
70        )
71        .await
72        .map_err(Error::from)?;
73        tx.commit().await.map_err(Error::from)?;
74        Ok(())
75    }
76
77    /// Store a new send intent, or re-queue an existing failed intent with
78    /// the same quote id.
79    pub async fn create_or_retry_failed_send_intent(
80        &self,
81        intent: &SendIntentRecord,
82    ) -> Result<SendIntentRecord, Error> {
83        let mut tx = self
84            .kv_store
85            .begin_transaction()
86            .await
87            .map_err(Error::from)?;
88
89        let finalized = tx
90            .kv_read(
91                BDK_NAMESPACE,
92                FINALIZED_SEND_INTENT_QUOTE_ID_NAMESPACE,
93                &intent.quote_id,
94            )
95            .await
96            .map_err(Error::from)?;
97
98        if finalized.is_some() {
99            tx.rollback().await.map_err(Error::from)?;
100            return Err(Error::DuplicateQuoteId(intent.quote_id.clone()));
101        }
102
103        let active = tx
104            .kv_read(
105                BDK_NAMESPACE,
106                SEND_INTENT_QUOTE_ID_NAMESPACE,
107                &intent.quote_id,
108            )
109            .await
110            .map_err(Error::from)?;
111
112        let record = if let Some(intent_id_bytes) = active {
113            let intent_id_str = std::str::from_utf8(&intent_id_bytes)
114                .map_err(|e| Error::Wallet(format!("Invalid quote-id index entry: {}", e)))?;
115            let intent_id = Uuid::from_str(intent_id_str)
116                .map_err(|e| Error::Wallet(format!("Invalid indexed intent id: {}", e)))?;
117            let intent_bytes = tx
118                .kv_read(BDK_NAMESPACE, SEND_INTENT_NAMESPACE, &intent_id.to_string())
119                .await
120                .map_err(Error::from)?
121                .ok_or(Error::SendIntentNotFound(intent_id))?;
122            let existing: SendIntentRecord = serde_json::from_slice(&intent_bytes)?;
123
124            if !matches!(existing.state, SendIntentState::Failed { .. }) {
125                tx.rollback().await.map_err(Error::from)?;
126                return Err(Error::DuplicateQuoteId(intent.quote_id.clone()));
127            }
128
129            SendIntentRecord {
130                intent_id,
131                quote_id: intent.quote_id.clone(),
132                address: intent.address.clone(),
133                amount_sat: intent.amount_sat,
134                max_fee_amount_sat: intent.max_fee_amount_sat,
135                tier: intent.tier,
136                metadata: intent.metadata.clone(),
137                state: intent.state.clone(),
138            }
139        } else {
140            tx.kv_write(
141                BDK_NAMESPACE,
142                SEND_INTENT_QUOTE_ID_NAMESPACE,
143                &intent.quote_id,
144                intent.intent_id.to_string().as_bytes(),
145            )
146            .await
147            .map_err(Error::from)?;
148            intent.clone()
149        };
150
151        let serialized = serde_json::to_vec(&record)?;
152        tx.kv_write(
153            BDK_NAMESPACE,
154            SEND_INTENT_NAMESPACE,
155            &record.intent_id.to_string(),
156            &serialized,
157        )
158        .await
159        .map_err(Error::from)?;
160        tx.commit().await.map_err(Error::from)?;
161        Ok(record)
162    }
163
164    /// Get a send intent by ID
165    pub async fn get_send_intent(
166        &self,
167        intent_id: &Uuid,
168    ) -> Result<Option<SendIntentRecord>, Error> {
169        self.get_record::<SendIntentRecord>(&intent_id.to_string())
170            .await
171    }
172
173    /// Update a send intent's state
174    pub async fn update_send_intent(
175        &self,
176        intent_id: &Uuid,
177        new_state: &SendIntentState,
178    ) -> Result<(), Error> {
179        let key = intent_id.to_string();
180        if self.get_send_intent(intent_id).await?.is_none() {
181            return Err(Error::SendIntentNotFound(*intent_id));
182        }
183
184        self.update_record_state::<SendIntentRecord, SendIntentState>(&key, new_state)
185            .await
186    }
187
188    /// Delete a send intent
189    pub async fn delete_send_intent(&self, intent_id: &Uuid) -> Result<(), Error> {
190        let Some(intent) = self.get_send_intent(intent_id).await? else {
191            return Ok(());
192        };
193
194        let mut tx = self
195            .kv_store
196            .begin_transaction()
197            .await
198            .map_err(Error::from)?;
199        tx.kv_remove(BDK_NAMESPACE, SEND_INTENT_NAMESPACE, &intent_id.to_string())
200            .await
201            .map_err(Error::from)?;
202        tx.kv_remove(
203            BDK_NAMESPACE,
204            SEND_INTENT_QUOTE_ID_NAMESPACE,
205            &intent.quote_id,
206        )
207        .await
208        .map_err(Error::from)?;
209        tx.commit().await.map_err(Error::from)?;
210        Ok(())
211    }
212
213    /// Get all send intents
214    pub async fn get_all_send_intents(&self) -> Result<Vec<SendIntentRecord>, Error> {
215        self.list_records::<SendIntentRecord>().await
216    }
217
218    /// Get all pending send intents (filtering by state)
219    pub async fn get_pending_send_intents(&self) -> Result<Vec<SendIntentRecord>, Error> {
220        let all = self.get_all_send_intents().await?;
221        Ok(all
222            .into_iter()
223            .filter(|i| matches!(i.state, SendIntentState::Pending { .. }))
224            .collect())
225    }
226
227    /// Store a failed pre-sign send attempt tombstone.
228    pub async fn add_failed_send_attempt(
229        &self,
230        record: &FailedSendAttemptRecord,
231    ) -> Result<(), Error> {
232        self.put_record(record).await
233    }
234
235    /// List failed pre-sign send attempts for a quote id.
236    pub async fn get_failed_send_attempts_by_quote_id(
237        &self,
238        quote_id: &str,
239    ) -> Result<Vec<FailedSendAttemptRecord>, Error> {
240        let all = self.list_records::<FailedSendAttemptRecord>().await?;
241        Ok(all
242            .into_iter()
243            .filter(|record| record.quote_id == quote_id)
244            .collect())
245    }
246
247    // ── Send Batch storage ───────────────────────────────────────────
248
249    /// Store a new send batch
250    pub async fn store_send_batch(&self, batch: &SendBatchRecord) -> Result<(), Error> {
251        self.put_record(batch).await
252    }
253
254    /// Get a send batch by ID
255    pub async fn get_send_batch(&self, batch_id: &Uuid) -> Result<Option<SendBatchRecord>, Error> {
256        self.get_record::<SendBatchRecord>(&batch_id.to_string())
257            .await
258    }
259
260    /// Update a send batch's state
261    pub async fn update_send_batch(
262        &self,
263        batch_id: &Uuid,
264        new_state: &SendBatchState,
265    ) -> Result<(), Error> {
266        let key = batch_id.to_string();
267        if self.get_send_batch(batch_id).await?.is_none() {
268            return Err(Error::SendBatchNotFound(*batch_id));
269        }
270
271        self.update_record_state::<SendBatchRecord, SendBatchState>(&key, new_state)
272            .await
273    }
274
275    /// Delete a send batch
276    pub async fn delete_send_batch(&self, batch_id: &Uuid) -> Result<(), Error> {
277        self.delete_record::<SendBatchRecord>(&batch_id.to_string())
278            .await
279    }
280
281    /// Get all send batches
282    pub async fn get_all_send_batches(&self) -> Result<Vec<SendBatchRecord>, Error> {
283        self.list_records::<SendBatchRecord>().await
284    }
285
286    // ── Finalized Intent storage (tombstones) ────────────────────────
287
288    /// Look up a finalized intent tombstone by intent ID.
289    pub async fn get_finalized_intent(
290        &self,
291        intent_id: &Uuid,
292    ) -> Result<Option<FinalizedSendIntentRecord>, Error> {
293        self.get_record::<FinalizedSendIntentRecord>(&intent_id.to_string())
294            .await
295    }
296
297    /// Look up a finalized intent tombstone by quote ID.
298    pub async fn get_finalized_intent_by_quote_id(
299        &self,
300        quote_id: &str,
301    ) -> Result<Option<FinalizedSendIntentRecord>, Error> {
302        let Some(intent_id_bytes) = self
303            .kv_store
304            .kv_read(
305                BDK_NAMESPACE,
306                FINALIZED_SEND_INTENT_QUOTE_ID_NAMESPACE,
307                quote_id,
308            )
309            .await
310            .map_err(Error::from)?
311        else {
312            return Ok(None);
313        };
314
315        let intent_id_str = std::str::from_utf8(&intent_id_bytes)
316            .map_err(|e| Error::Wallet(format!("Invalid intent-id index entry: {}", e)))?;
317        let intent_id = Uuid::from_str(intent_id_str)
318            .map_err(|e| Error::Wallet(format!("Invalid indexed intent id: {}", e)))?;
319
320        self.get_record::<FinalizedSendIntentRecord>(&intent_id.to_string())
321            .await
322    }
323
324    /// Look up a send intent by quote ID.
325    ///
326    /// Scans all active intents and returns the first match.
327    pub async fn get_send_intent_by_quote_id(
328        &self,
329        quote_id: &str,
330    ) -> Result<Option<SendIntentRecord>, Error> {
331        let Some(intent_id_bytes) = self
332            .kv_store
333            .kv_read(BDK_NAMESPACE, SEND_INTENT_QUOTE_ID_NAMESPACE, quote_id)
334            .await
335            .map_err(Error::from)?
336        else {
337            return Ok(None);
338        };
339
340        let intent_id = std::str::from_utf8(&intent_id_bytes)
341            .map_err(|e| Error::Wallet(format!("Invalid quote-id index entry: {}", e)))?;
342        let intent_id = Uuid::from_str(intent_id)
343            .map_err(|e| Error::Wallet(format!("Invalid indexed intent id: {}", e)))?;
344
345        self.get_send_intent(&intent_id).await
346    }
347
348    /// Atomically finalize an active send intent and create a tombstone.
349    pub async fn finalize_send_intent(
350        &self,
351        intent_id: &Uuid,
352        record: &FinalizedSendIntentRecord,
353    ) -> Result<(), Error> {
354        let Some(intent) = self.get_send_intent(intent_id).await? else {
355            return Err(Error::SendIntentNotFound(*intent_id));
356        };
357
358        let serialized = serde_json::to_vec(record)?;
359        let mut tx = self
360            .kv_store
361            .begin_transaction()
362            .await
363            .map_err(Error::from)?;
364        tx.kv_write(
365            BDK_NAMESPACE,
366            FINALIZED_INTENT_NAMESPACE,
367            &record.intent_id.to_string(),
368            &serialized,
369        )
370        .await
371        .map_err(Error::from)?;
372        tx.kv_write(
373            BDK_NAMESPACE,
374            FINALIZED_SEND_INTENT_QUOTE_ID_NAMESPACE,
375            &intent.quote_id,
376            record.intent_id.to_string().as_bytes(),
377        )
378        .await
379        .map_err(Error::from)?;
380        tx.kv_remove(BDK_NAMESPACE, SEND_INTENT_NAMESPACE, &intent_id.to_string())
381            .await
382            .map_err(Error::from)?;
383        tx.kv_remove(
384            BDK_NAMESPACE,
385            SEND_INTENT_QUOTE_ID_NAMESPACE,
386            &intent.quote_id,
387        )
388        .await
389        .map_err(Error::from)?;
390        tx.commit().await.map_err(Error::from)?;
391        Ok(())
392    }
393}