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