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