bark-wallet 0.3.0

Wallet library and CLI for the bitcoin Ark protocol built by Second
Documentation

mod error;
mod package;
mod states;

pub use self::package::{
	ChildTransactionInfo, ExitCpfpRequest, ExitTransactionPackage, FeeInfo, RbfRequirement,
	TransactionInfo,
};
pub use self::error::ExitError;
pub use self::states::{
	ExitTx, ExitTxStatus, ExitTxOrigin, ExitStartState, ExitProcessingState, ExitAwaitingDeltaState,
	ExitClaimableState, ExitClaimInProgressState, ExitClaimedState, ExitVtxoAlreadySpentState,
};

use ark::VtxoId;
use bitcoin::Txid;

use bitcoin_ext::{BlockDelta, BlockHeight, BlockRef, TxStatus};

/// A utility type to wrap ExitState children so they can be easily serialized. This also helps with
/// debugging a lot!
#[derive(Clone, Debug, PartialEq, Eq, Serialize, Deserialize)]
#[serde(tag = "type", rename_all = "kebab-case")]
pub enum ExitState {
	Start(ExitStartState),
	Processing(ExitProcessingState),
	AwaitingDelta(ExitAwaitingDeltaState),
	Claimable(ExitClaimableState),
	ClaimInProgress(ExitClaimInProgressState),
	/// Terminal state: the exit is fully complete, and the VTXO has been claimed by the user or
	/// spent by another user who owns a VTXO that is deeper in the tree than this one.
	///
	/// Note: The circumstances in which the latter can occur are typically when the user has stale
	/// data and is trying to exit an already-spent VTXO.
	Claimed(ExitClaimedState),
	/// Terminal state: the exit cannot proceed because the VTXO has already been spent offchain. A
	/// user can start a unilateral exit for a VTXO but later spend it via a refresh, arkoor, etc,
	/// in that situation an exit will enter this state.
	VtxoAlreadySpent(ExitVtxoAlreadySpentState),
}

impl ExitState {
	pub fn new_start(tip: BlockHeight) -> Self {
		ExitState::Start(ExitStartState { tip_height: tip })
	}

	pub fn new_processing<T: IntoIterator<Item = Txid>>(tip: BlockHeight, txids: T) -> Self {
		ExitState::Processing(ExitProcessingState {
			tip_height: tip,
			transactions: txids.into_iter()
				.map(|id| ExitTx {
					txid: id,
					status: ExitTxStatus::VerifyInputs,
				})
				.collect::<Vec<_>>(),
		})
	}

	pub fn new_processing_from_transactions(tip: BlockHeight, transactions: Vec<ExitTx>) -> Self {
		ExitState::Processing(ExitProcessingState {
			tip_height: tip,
			transactions,
		})
	}

	pub fn new_awaiting_delta(
		tip: BlockHeight,
		confirmed_block: BlockRef,
		wait_delta: BlockDelta
	) -> Self {
		debug_assert_ne!(wait_delta, 0, "wait delta must be non-zero");
		let claimable_height = confirmed_block.height + wait_delta as BlockHeight;
		ExitState::AwaitingDelta(ExitAwaitingDeltaState {
			tip_height: tip,
			confirmed_block,
			claimable_height,
		})
	}

	pub fn new_claimable(
		tip: BlockHeight,
		claimable_since: BlockRef,
		last_scanned_block: Option<BlockRef>
	) -> Self {
		ExitState::Claimable(ExitClaimableState {
			tip_height: tip,
			claimable_since,
			last_scanned_block,
		})
	}

	pub fn new_claim_in_progress(
		tip: BlockHeight,
		claimable_since: BlockRef,
		claim_txid: Txid
	) -> Self {
		ExitState::ClaimInProgress(ExitClaimInProgressState {
			tip_height: tip,
			claimable_since,
			claim_txid,
		})
	}

	pub fn new_claimed(tip: BlockHeight, txid: Txid, block: BlockRef) -> Self {
		ExitState::Claimed(ExitClaimedState {
			tip_height: tip,
			txid,
			block,
		})
	}

	pub fn new_vtxo_already_spent(tip: BlockHeight) -> Self {
		ExitState::VtxoAlreadySpent(ExitVtxoAlreadySpentState { tip_height: tip })
	}

	/// Checks if the state is awaiting the confirmation of every exit transaction in the tree and
	/// the exit delta required for the VTXO to become claimable.
	///
	/// Note: This excludes the claimable state, use [ExitState::is_claimable] for that.
	pub fn is_pending(&self) -> bool {
		match self {
			ExitState::Start(_) => true,
			ExitState::Processing(_) => true,
			ExitState::AwaitingDelta(_) => true,
			_ => false,
		}
	}

	/// A simple helper for [ExitState::Claimable], at this point an exit can be spent on-chain
	/// and redeemed into a UTXO controlled by the user.
	pub fn is_claimable(&self) -> bool {
		match self {
			ExitState::Claimable(_) => true,
			_ => false,
		}
	}

	pub fn requires_confirmations(&self) -> bool {
		match self {
			ExitState::Processing(s) => {
				s.transactions.iter().any(|s| match s.status {
					ExitTxStatus::AwaitingInputConfirmation { .. } => true,
					ExitTxStatus::AwaitingConfirmation { .. } => true,
					_ => false,
				})
			},
			ExitState::AwaitingDelta(_) => true,
			ExitState::ClaimInProgress(_) => true,
			_ => false,
		}
	}

	pub fn claimable_height(&self) -> Option<BlockHeight> {
		match self {
			ExitState::AwaitingDelta(s) => Some(s.claimable_height),
			ExitState::Claimable(s) => Some(s.claimable_since.height),
			ExitState::ClaimInProgress(s) => Some(s.claimable_since.height),
			_ => None,
		}
	}

	/// True once every exit transaction has confirmed on-chain (i.e. the exit has reached
	/// at least [`ExitState::AwaitingDelta`]). At that point the VTXO can be considered
	/// [crate::vtxo::VtxoStateKind::Exited]: the underlying onchain outpoint is committed
	/// to the exit chain, the server can see it and will refuse to service the VTXO for
	/// any payment operations offchain, and there's nothing left to undo client-side.
	///
	/// Note: we deliberately don't flip the VTXO at `Processing` — even with every tx
	/// broadcast, mempool eviction is still possible until confirmation, so we hold off
	/// to keep `Exited` an accurate "this is gone" signal.
	pub fn warrants_exited_vtxo(&self) -> bool {
		match self {
			ExitState::Start(_) => false,
			ExitState::Processing(_) => false,
			ExitState::AwaitingDelta(_) => true,
			ExitState::Claimable(_) => true,
			ExitState::ClaimInProgress(_) => true,
			ExitState::Claimed(_) => true,
			ExitState::VtxoAlreadySpent(_) => false,
		}
	}
}

#[derive(Debug, Clone, PartialEq, Eq)]
pub struct ExitProgressStatus {
	/// The ID of the VTXO that is being unilaterally exited
	pub vtxo_id: VtxoId,
	/// The current state of the exit transaction
	pub state: ExitState,
	/// Any error that occurred during the exit process
	pub error: Option<ExitError>,
}

#[derive(Debug, Clone, PartialEq, Eq)]
pub struct ExitTransactionStatus {
	/// The ID of the VTXO that is being unilaterally exited
	pub vtxo_id: VtxoId,
	/// The current state of the exit transaction
	pub state: ExitState,
	/// The history of each state the exit transaction has gone through
	pub history: Option<Vec<ExitState>>,
	/// Each exit transaction package required for the unilateral exit
	pub transactions: Vec<ExitTransactionPackage>,
}

#[derive(Clone, Copy, Debug,  Eq, PartialEq)]
pub struct ExitChildStatus {
	pub txid: Txid,
	pub status: TxStatus,
	pub origin: ExitTxOrigin,
	pub fee_info: Option<FeeInfo>,
}