bark/persist/mod.rs
1//! Persistence abstractions for Bark wallets.
2//!
3//! This module defines the [BarkPersister] trait and related data models used by the
4//! wallet to store and retrieve state. Implementors can provide their own storage backends
5//! (e.g., SQLite, PostgreSQL, in-memory, mobile key/value stores) by implementing the
6//! [BarkPersister] trait.
7//!
8//! Design goals
9//! - Clear separation between wallet logic and storage.
10//! - Transactional semantics where appropriate (round state transitions, movement recording).
11//! - Portability across different platforms and environments.
12//!
13//! Typical usage
14//! - Applications construct a concrete persister (for example, a SQLite-backed client) and
15//! pass it to the [crate::Wallet]. The [crate::Wallet] only depends on this trait for reads/writes.
16//! - Custom wallet implementations can reuse this trait to remain compatible with Bark
17//! storage expectations without depending on a specific database.
18//! - A default rusqlite implementation is provided by [sqlite::SqliteClient].
19
20pub mod adaptor;
21pub mod models;
22#[cfg(feature = "sqlite")]
23pub mod sqlite;
24
25
26use bitcoin::{Amount, Transaction, Txid};
27use bitcoin::secp256k1::PublicKey;
28use chrono::DateTime;
29use lightning_invoice::Bolt11Invoice;
30#[cfg(feature = "onchain-bdk")]
31use bdk_wallet::ChangeSet;
32
33use ark::{Vtxo, VtxoId};
34use ark::lightning::{Invoice, PaymentHash, Preimage};
35use ark::vtxo::Full;
36use bitcoin_ext::BlockDelta;
37
38use crate::WalletProperties;
39use crate::exit::ExitTxOrigin;
40use crate::persist::models::{
41 LightningReceive, LightningSend, PendingBoard, RoundStateId, StoredExit, StoredRoundState, Unlocked,
42 PendingOffboard,
43};
44use crate::movement::{Movement, MovementId, MovementStatus, MovementSubsystem};
45use crate::round::RoundState;
46use crate::vtxo::{VtxoState, VtxoStateKind, WalletVtxo};
47
48/// Storage interface for Bark wallets.
49///
50/// Implement this trait to plug a custom persistence backend. The wallet uses it to:
51/// - Initialize and read wallet properties and configuration.
52/// - Record movements (spends/receives), recipients, and enforce [Vtxo] state transitions.
53/// - Manage round lifecycles (attempts, pending confirmation, confirmations/cancellations).
54/// - Persist ephemeral protocol artifacts (e.g., secret nonces) transactionally.
55/// - Track Lightning receives and preimage revelation.
56/// - Track exit-related data and associated child transactions.
57/// - Persist the last synchronized Ark block height.
58///
59/// Feature integration:
60/// - With the `onchain-bdk` feature, methods are provided to initialize and persist a BDK
61/// wallet ChangeSet in the same storage.
62///
63/// Notes for implementors:
64/// - Ensure that operations that change multiple records (e.g., registering a movement,
65/// storing round state transitions) are executed transactionally.
66/// - Enforce state integrity by verifying allowed_old_states before updating a [Vtxo] state.
67/// - If your backend is not thread-safe, prefer a short-lived connection per call or use
68/// an internal pool with checked-out connections per operation.
69/// - Return precise errors so callers can surface actionable diagnostics.
70#[cfg_attr(target_arch = "wasm32", async_trait(?Send))]
71#[cfg_attr(not(target_arch = "wasm32"), async_trait)]
72pub trait BarkPersister: Send + Sync + 'static {
73 /// Check if the wallet is initialized.
74 ///
75 /// Returns:
76 /// - `Ok(true)` if the wallet is initialized.
77 /// - `Ok(false)` if the wallet is not initialized.
78 ///
79 /// Errors:
80 /// - Returns an error if the query fails.
81 async fn is_initialized(&self) -> anyhow::Result<bool> {
82 Ok(self.read_properties().await?.is_some())
83 }
84
85 /// Initialize a wallet in storage with the provided properties.
86 ///
87 /// Call exactly once per wallet database. Subsequent calls should fail to prevent
88 /// accidental re-initialization.
89 ///
90 /// Parameters:
91 /// - properties: WalletProperties to persist (e.g., network, descriptors, metadata).
92 ///
93 /// Returns:
94 /// - `Ok(())` on success.
95 ///
96 /// Errors:
97 /// - Returns an error if the wallet is already initialized or if persistence fails.
98 async fn init_wallet(&self, properties: &WalletProperties) -> anyhow::Result<()>;
99
100 /// Initialize the onchain BDK wallet and return any previously stored ChangeSet.
101 ///
102 /// Must be called before storing any new BDK changesets to bootstrap the BDK state.
103 ///
104 /// Feature: only available with `onchain-bdk`.
105 ///
106 /// Returns:
107 /// - `Ok(ChangeSet)` containing the previously persisted BDK state (possibly empty).
108 ///
109 /// Errors:
110 /// - Returns an error if the BDK state cannot be created or loaded.
111 #[cfg(feature = "onchain-bdk")]
112 async fn initialize_bdk_wallet(&self) -> anyhow::Result<ChangeSet>;
113
114 /// Persist an incremental BDK ChangeSet.
115 ///
116 /// The changeset should be applied atomically. Callers typically obtain the changeset
117 /// from a BDK wallet instance after mutating wallet state (e.g., sync).
118 ///
119 /// Feature: only available with `onchain-bdk`.
120 ///
121 /// Parameters:
122 /// - changeset: The BDK ChangeSet to persist.
123 ///
124 /// Errors:
125 /// - Returns an error if the changeset cannot be written.
126 #[cfg(feature = "onchain-bdk")]
127 async fn store_bdk_wallet_changeset(&self, changeset: &ChangeSet) -> anyhow::Result<()>;
128
129 /// Read wallet properties from storage.
130 ///
131 /// Returns:
132 /// - `Ok(Some(WalletProperties))` if the wallet has been initialized.
133 /// - `Ok(None)` if no wallet exists yet.
134 ///
135 /// Errors:
136 /// - Returns an error on I/O or deserialization failures.
137 async fn read_properties(&self) -> anyhow::Result<Option<WalletProperties>>;
138
139 /// Set the server public key in wallet properties.
140 ///
141 /// This is used to store the server pubkey for existing wallets that were
142 /// created before server pubkey tracking was added. Once set, the wallet
143 /// will verify the server pubkey on every connection.
144 ///
145 /// Parameters:
146 /// - server_pubkey: The server's public key to store.
147 ///
148 /// Errors:
149 /// - Returns an error if the update fails.
150 async fn set_server_pubkey(&self, server_pubkey: PublicKey) -> anyhow::Result<()>;
151
152 /// Creates a new movement in the given state, ready to be updated.
153 ///
154 /// Parameters:
155 /// - status: The desired status for the new movement.
156 /// - subsystem: The subsystem that created the movement.
157 /// - time: The time the movement should be marked as created.
158 ///
159 /// Returns:
160 /// - `Ok(MovementId)` of the newly created movement.
161 ///
162 /// Errors:
163 /// - Returns an error if the movement is unable to be created.
164 async fn create_new_movement(&self,
165 status: MovementStatus,
166 subsystem: &MovementSubsystem,
167 time: DateTime<chrono::Local>,
168 ) -> anyhow::Result<MovementId>;
169
170 /// Persists the given movement state.
171 ///
172 /// Parameters:
173 /// - movement: The movement and its associated data to be persisted.
174 ///
175 /// Errors:
176 /// - Returns an error if updating the movement fails for any reason.
177 async fn update_movement(&self, movement: &Movement) -> anyhow::Result<()>;
178
179 /// Gets the movement with the given [MovementId].
180 ///
181 /// Parameters:
182 /// - movement_id: The ID of the movement to retrieve.
183 ///
184 /// Returns:
185 /// - `Ok(Movement)` if the movement exists.
186 ///
187 /// Errors:
188 /// - If the movement does not exist.
189 /// - If retrieving the movement fails.
190 async fn get_movement_by_id(&self, movement_id: MovementId) -> anyhow::Result<Movement>;
191
192 /// Gets every stored movement.
193 ///
194 /// Returns:
195 /// - `Ok(Vec<Movement>)` containing all movements, empty if none exist.
196 ///
197 /// Errors:
198 /// - If retrieving the movements fails.
199 async fn get_all_movements(&self) -> anyhow::Result<Vec<Movement>>;
200
201 /// Store a pending board.
202 ///
203 /// Parameters:
204 /// - vtxo: The [Vtxo] to store.
205 /// - funding_txid: The funding [Txid].
206 /// - movement_id: The [MovementId] associated with this board.
207 ///
208 /// Errors:
209 /// - Returns an error if the board cannot be stored.
210 async fn store_pending_board(
211 &self,
212 vtxo: &Vtxo<Full>,
213 funding_tx: &Transaction,
214 movement_id: MovementId,
215 ) -> anyhow::Result<()>;
216
217 /// Remove a pending board.
218 ///
219 /// Parameters:
220 /// - vtxo_id: The [VtxoId] to remove.
221 ///
222 /// Errors:
223 /// - Returns an error if the board cannot be removed.
224 async fn remove_pending_board(&self, vtxo_id: &VtxoId) -> anyhow::Result<()>;
225
226 /// Get the [VtxoId] for each pending board.
227 ///
228 /// Returns:
229 /// - `Ok(Vec<VtxoId>)` possibly empty.
230 ///
231 /// Errors:
232 /// - Returns an error if the query fails.
233 async fn get_all_pending_board_ids(&self) -> anyhow::Result<Vec<VtxoId>>;
234
235 /// Get the [PendingBoard] associated with the given [VtxoId].
236 ///
237 /// Returns:
238 /// - `Ok(Some(PendingBoard))` if a matching board exists
239 /// - `Ok(None)` if no matching board exists
240 ///
241 /// Errors:
242 /// - Returns an error if the query fails.
243 async fn get_pending_board_by_vtxo_id(&self, vtxo_id: VtxoId) -> anyhow::Result<Option<PendingBoard>>;
244
245 /// Store a new ongoing round state and lock the VTXOs in round
246 ///
247 /// Parameters:
248 /// - `round_state`: the state to store
249 ///
250 /// Returns:
251 /// - `RoundStateId`: the storaged ID of the new state
252 ///
253 /// Errors:
254 /// - returns an error of the new round state could not be stored or the VTXOs
255 /// couldn't be marked as locked
256 async fn store_round_state_lock_vtxos(&self, round_state: &RoundState) -> anyhow::Result<RoundStateId>;
257
258 /// Update an existing stored pending round state
259 ///
260 /// Parameters:
261 /// - `round_state`: the round state to update
262 ///
263 /// Errors:
264 /// - returns an error of the existing round state could not be found or updated
265 async fn update_round_state(&self, round_state: &StoredRoundState) -> anyhow::Result<()>;
266
267 /// Remove a pending round state from the db
268 ///
269 /// Parameters:
270 /// - `round_state`: the round state to remove
271 ///
272 /// Errors:
273 /// - returns an error of the existing round state could not be found or removed
274 async fn remove_round_state(&self, round_state: &StoredRoundState) -> anyhow::Result<()>;
275
276 /// Load a single round state by its id
277 ///
278 /// Returns:
279 /// - `Option<StoredRoundState>`: the stored round state if found, `None` otherwise
280 ///
281 /// Errors:
282 /// - returns an error of the states could not be succesfully retrieved
283 async fn get_round_state_by_id(&self, id: RoundStateId) -> anyhow::Result<Option<StoredRoundState<Unlocked>>>;
284
285 /// Load all pending round states from the db
286 ///
287 /// Returns:
288 /// - `Vec<RoundStateId>`: unordered vector with all stored round state ids
289 ///
290 /// Errors:
291 /// - returns an error of the ids could not be succesfully retrieved
292 async fn get_pending_round_state_ids(&self) -> anyhow::Result<Vec<RoundStateId>>;
293
294 /// Stores VTXOs with their initial state.
295 ///
296 /// This operation is idempotent: if a VTXO already exists (same `id`), the
297 /// implementation should succeed without modifying the existing VTXO or its
298 /// state. This allows safe retries during crash recovery scenarios.
299 ///
300 /// # Parameters
301 /// - `vtxos`: Slice of VTXO and state pairs to store.
302 ///
303 /// # Behavior
304 /// - For each VTXO that does not exist: inserts the VTXO and its initial state.
305 /// - For each VTXO that already exists: no-op for that VTXO.
306 ///
307 /// # Errors
308 /// - Returns an error if the storage operation fails.
309 async fn store_vtxos(
310 &self,
311 vtxos: &[(&Vtxo<Full>, &VtxoState)],
312 ) -> anyhow::Result<()>;
313
314 /// Fetch a wallet [Vtxo] with its current state by ID.
315 ///
316 /// Parameters:
317 /// - id: [VtxoId] to look up.
318 ///
319 /// Returns:
320 /// - `Ok(Some(WalletVtxo))` if found,
321 /// - `Ok(None)` otherwise.
322 ///
323 /// Errors:
324 /// - Returns an error if the lookup fails.
325 async fn get_wallet_vtxo(&self, id: VtxoId) -> anyhow::Result<Option<WalletVtxo>>;
326
327 /// Fetch all wallet VTXOs in the database.
328 ///
329 /// Returns:
330 /// - `Ok(Vec<WalletVtxo>)` possibly empty.
331 ///
332 /// Errors:
333 /// - Returns an error if the query fails.
334 async fn get_all_vtxos(&self) -> anyhow::Result<Vec<WalletVtxo>>;
335
336 /// Fetch all wallet VTXOs whose state matches any of the provided kinds.
337 ///
338 /// Parameters:
339 /// - state: Slice of `VtxoStateKind` filters.
340 ///
341 /// Returns:
342 /// - `Ok(Vec<WalletVtxo>)` possibly empty.
343 ///
344 /// Errors:
345 /// - Returns an error if the query fails.
346 async fn get_vtxos_by_state(&self, state: &[VtxoStateKind]) -> anyhow::Result<Vec<WalletVtxo>>;
347
348 /// Remove a [Vtxo] by ID.
349 ///
350 /// Parameters:
351 /// - id: `VtxoId` to remove.
352 ///
353 /// Returns:
354 /// - `Ok(Some(Vtxo))` with the removed [Vtxo] data if it existed,
355 /// - `Ok(None)` otherwise.
356 ///
357 /// Errors:
358 /// - Returns an error if the delete operation fails.
359 async fn remove_vtxo(&self, id: VtxoId) -> anyhow::Result<Option<Vtxo<Full>>>;
360
361 /// Check whether a [Vtxo] is already marked spent.
362 ///
363 /// Parameters:
364 /// - id: VtxoId to check.
365 ///
366 /// Returns:
367 /// - `Ok(true)` if spent,
368 /// - `Ok(false)` if not found or not spent.
369 ///
370 /// Errors:
371 /// - Returns an error if the lookup fails.
372 async fn has_spent_vtxo(&self, id: VtxoId) -> anyhow::Result<bool>;
373
374 /// Store a newly derived/assigned [Vtxo] public key index mapping.
375 ///
376 /// Parameters:
377 /// - index: Derivation index.
378 /// - public_key: PublicKey at that index.
379 ///
380 /// Errors:
381 /// - Returns an error if the mapping cannot be stored.
382 async fn store_vtxo_key(&self, index: u32, public_key: PublicKey) -> anyhow::Result<()>;
383
384 /// Get the last revealed/used [Vtxo] key index.
385 ///
386 /// Returns:
387 /// - `Ok(Some(u32))` if a key was stored
388 /// - `Ok(None)` otherwise.
389 ///
390 /// Errors:
391 /// - Returns an error if the query fails.
392 async fn get_last_vtxo_key_index(&self) -> anyhow::Result<Option<u32>>;
393
394 /// Retrieves the derivation index of the provided [PublicKey] from the database
395 ///
396 /// Returns:
397 /// - `Ok(Some(u32))` if the key was stored.
398 /// - `Ok(None)` if the key was not stored.
399 ///
400 /// Errors:
401 /// - Returns an error if the query fails.
402 async fn get_public_key_idx(&self, public_key: &PublicKey) -> anyhow::Result<Option<u32>>;
403
404 /// Retrieves the mailbox checkpoint from the database
405 ///
406 /// Returns:
407 /// - `Ok(u64)` the stored checkpoint.
408 ///
409 /// Errors:
410 /// - Returns an error if the query fails.
411 async fn get_mailbox_checkpoint(&self) -> anyhow::Result<u64>;
412
413 /// Update the mailbox checkpoint to the new checkpoint
414 ///
415 /// Returns:
416 ///
417 ///
418 /// Errors:
419 /// - Returns error when the query fails
420 /// - Returns error when the provided checkpoint is smaller than the existing checkpoint
421 async fn store_mailbox_checkpoint(&self, checkpoint: u64) -> anyhow::Result<()>;
422
423 /// Store a new pending lightning send.
424 ///
425 /// Parameters:
426 /// - invoice: The invoice of the pending lightning send.
427 /// - amount: The amount of the pending lightning send.
428 /// - fee: The fee of the pending lightning send.
429 /// - vtxos: The vtxos of the pending lightning send.
430 /// - movement_id: The movement ID associated with this send.
431 ///
432 /// Errors:
433 /// - Returns an error if the pending lightning send cannot be stored.
434 async fn store_new_pending_lightning_send(
435 &self,
436 invoice: &Invoice,
437 amount: Amount,
438 fee: Amount,
439 vtxos: &[VtxoId],
440 movement_id: MovementId,
441 ) -> anyhow::Result<LightningSend>;
442
443 /// Get all pending lightning sends.
444 ///
445 /// Returns:
446 /// - `Ok(Vec<LightningSend>)` possibly empty.
447 ///
448 /// Errors:
449 /// - Returns an error if the query fails.
450 async fn get_all_pending_lightning_send(&self) -> anyhow::Result<Vec<LightningSend>>;
451
452 /// Mark a lightning send as finished.
453 ///
454 /// Parameters:
455 /// - payment_hash: The [PaymentHash] of the lightning send to update.
456 /// - preimage: The [Preimage] of the successful lightning send.
457 ///
458 /// Errors:
459 /// - Returns an error if the lightning send cannot be updated.
460 async fn finish_lightning_send(
461 &self,
462 payment_hash: PaymentHash,
463 preimage: Option<Preimage>,
464 ) -> anyhow::Result<()>;
465
466 /// Remove a lightning send.
467 ///
468 /// Parameters:
469 /// - payment_hash: The [PaymentHash] of the lightning send to remove.
470 ///
471 /// Errors:
472 /// - Returns an error if the lightning send cannot be removed.
473 async fn remove_lightning_send(&self, payment_hash: PaymentHash) -> anyhow::Result<()>;
474
475 /// Get a lightning send by payment hash
476 ///
477 /// Parameters:
478 /// - payment_hash: The [PaymentHash] of the lightning send to get.
479 ///
480 /// Errors:
481 /// - Returns an error if the lookup fails.
482 async fn get_lightning_send(&self, payment_hash: PaymentHash) -> anyhow::Result<Option<LightningSend>>;
483
484 /// Store an incoming Lightning receive record.
485 ///
486 /// Parameters:
487 /// - payment_hash: Unique payment hash.
488 /// - preimage: Payment preimage (kept until disclosure).
489 /// - invoice: The associated BOLT11 invoice.
490 /// - htlc_recv_cltv_delta: The CLTV delta for the HTLC VTXO.
491 ///
492 /// Errors:
493 /// - Returns an error if the receive cannot be stored.
494 async fn store_lightning_receive(
495 &self,
496 payment_hash: PaymentHash,
497 preimage: Preimage,
498 invoice: &Bolt11Invoice,
499 htlc_recv_cltv_delta: BlockDelta,
500 ) -> anyhow::Result<()>;
501
502 /// Returns a list of all pending lightning receives
503 ///
504 /// Returns:
505 /// - `Ok(Vec<LightningReceive>)` possibly empty.
506 ///
507 /// Errors:
508 /// - Returns an error if the query fails.
509 async fn get_all_pending_lightning_receives(&self) -> anyhow::Result<Vec<LightningReceive>>;
510
511 /// Mark a Lightning receive preimage as revealed (e.g., after settlement).
512 ///
513 /// Parameters:
514 /// - payment_hash: The payment hash identifying the receive.
515 ///
516 /// Errors:
517 /// - Returns an error if the update fails or the receive does not exist.
518 async fn set_preimage_revealed(&self, payment_hash: PaymentHash) -> anyhow::Result<()>;
519
520 /// Set the VTXO IDs and [MovementId] for a [LightningReceive].
521 ///
522 /// Parameters:
523 /// - payment_hash: The payment hash identifying the receive.
524 /// - htlc_vtxo_ids: The VTXO IDs to set.
525 /// - movement_id: The movement ID associated with the invoice.
526 ///
527 /// Errors:
528 /// - Returns an error if the update fails or the receive does not exist.
529 async fn update_lightning_receive(
530 &self,
531 payment_hash: PaymentHash,
532 htlc_vtxo_ids: &[VtxoId],
533 movement_id: MovementId,
534 ) -> anyhow::Result<()>;
535
536 /// Fetch a Lightning receive by its payment hash.
537 ///
538 /// Parameters:
539 /// - payment_hash: The payment hash to look up.
540 ///
541 /// Returns:
542 /// - `Ok(Some(LightningReceive))` if found,
543 /// - `Ok(None)` otherwise.
544 ///
545 /// Errors:
546 /// - Returns an error if the lookup fails.
547 async fn fetch_lightning_receive_by_payment_hash(
548 &self,
549 payment_hash: PaymentHash,
550 ) -> anyhow::Result<Option<LightningReceive>>;
551
552 /// Mark a Lightning receive as finished by its payment hash.
553 ///
554 /// Parameters:
555 /// - payment_hash: The payment hash of the record to mark finished
556 ///
557 /// Errors:
558 /// - Returns an error if the operation fails.
559 async fn finish_pending_lightning_receive(
560 &self,
561 payment_hash: PaymentHash,
562 ) -> anyhow::Result<()>;
563
564 /// Store an entry indicating a [Vtxo] is being exited.
565 ///
566 /// Parameters:
567 /// - exit: StoredExit describing the exit operation.
568 ///
569 /// Errors:
570 /// - Returns an error if the entry cannot be stored.
571 async fn store_exit_vtxo_entry(&self, exit: &StoredExit) -> anyhow::Result<()>;
572
573 /// Remove an exit entry for a given [Vtxo] ID.
574 ///
575 /// Parameters:
576 /// - id: VtxoId to remove from exit tracking.
577 ///
578 /// Errors:
579 /// - Returns an error if the removal fails.
580 async fn remove_exit_vtxo_entry(&self, id: &VtxoId) -> anyhow::Result<()>;
581
582 /// List all VTXOs currently tracked as being exited.
583 ///
584 /// Returns:
585 /// - `Ok(Vec<StoredExit>)` possibly empty.
586 ///
587 /// Errors:
588 /// - Returns an error if the query fails.
589 async fn get_exit_vtxo_entries(&self) -> anyhow::Result<Vec<StoredExit>>;
590
591 /// Store a child transaction related to an exit transaction.
592 ///
593 /// Parameters:
594 /// - exit_txid: The parent exit transaction ID.
595 /// - child_tx: The child bitcoin Transaction to store.
596 /// - origin: Metadata describing where the child came from (ExitTxOrigin).
597 ///
598 /// Errors:
599 /// - Returns an error if the transaction cannot be stored.
600 async fn store_exit_child_tx(
601 &self,
602 exit_txid: Txid,
603 child_tx: &Transaction,
604 origin: ExitTxOrigin,
605 ) -> anyhow::Result<()>;
606
607 /// Retrieve a stored child transaction for a given exit transaction ID.
608 ///
609 /// Parameters:
610 /// - exit_txid: The parent exit transaction ID.
611 ///
612 /// Returns:
613 /// - `Ok(Some((Transaction, ExitTxOrigin)))` if found,
614 /// - `Ok(None)` otherwise.
615 ///
616 /// Errors:
617 /// - Returns an error if the lookup fails.
618 async fn get_exit_child_tx(
619 &self,
620 exit_txid: Txid,
621 ) -> anyhow::Result<Option<(Transaction, ExitTxOrigin)>>;
622
623 /// Updates the state of the VTXO corresponding to the given [VtxoId], provided that their
624 /// current state is one of the given `allowed_states`.
625 ///
626 /// # Parameters
627 /// - `vtxo_id`: The ID of the [Vtxo] to update.
628 /// - `state`: The new state to be set for the specified [Vtxo].
629 /// - `allowed_states`: An iterable collection of allowed states ([VtxoStateKind]) that the
630 /// [Vtxo] must currently be in for their state to be updated to the new `state`.
631 ///
632 /// # Returns
633 /// - `Ok(WalletVtxo)` if the state update is successful.
634 /// - `Err(anyhow::Error)` if the VTXO fails to meet the required conditions,
635 /// or if another error occurs during the operation.
636 ///
637 /// # Errors
638 /// - Returns an error if the current state is not within the `allowed_states`.
639 /// - Returns an error for any other issues encountered during the operation.
640 async fn update_vtxo_state_checked(
641 &self,
642 vtxo_id: VtxoId,
643 new_state: VtxoState,
644 allowed_old_states: &[VtxoStateKind],
645 ) -> anyhow::Result<WalletVtxo>;
646
647 /// Store a pending offboard record.
648 ///
649 /// Parameters:
650 /// - pending: The [PendingOffboard] to store.
651 ///
652 /// Errors:
653 /// - Returns an error if the record cannot be stored.
654 async fn store_pending_offboard(
655 &self,
656 pending: &PendingOffboard,
657 ) -> anyhow::Result<()>;
658
659 /// Get all pending offboard records.
660 ///
661 /// Returns:
662 /// - `Ok(Vec<PendingOffboard>)` possibly empty.
663 ///
664 /// Errors:
665 /// - Returns an error if the query fails.
666 async fn get_pending_offboards(&self) -> anyhow::Result<Vec<PendingOffboard>>;
667
668 /// Remove a pending offboard record by its [MovementId].
669 ///
670 /// Parameters:
671 /// - movement_id: The [MovementId] to remove.
672 ///
673 /// Errors:
674 /// - Returns an error if the record cannot be removed.
675 async fn remove_pending_offboard(&self, movement_id: MovementId) -> anyhow::Result<()>;
676}