Skip to main content

Overview

Bark provides two ways to move funds from Ark to onchain Bitcoin:
  1. Offboarding: Cooperative exit with the server (fast and cheap)
  2. Unilateral Exit: Non-cooperative exit without server (trustless but slower)

Offboarding (Cooperative)

Offboarding works with the Ark server to efficiently move funds onchain.

offboard_all

Offboard all spendable VTXOs to a Bitcoin address.
pub async fn offboard_all(&self, address: bitcoin::Address) -> anyhow::Result<Txid>
address
bitcoin::Address
required
Bitcoin address to receive funds
Returns
Result<Txid>
Transaction ID of the offboard transaction
Example:
use bitcoin::Address;

let addr = Address::from_str("bc1q...")?.assume_checked();
let txid = wallet.offboard_all(addr).await?;

println!("Offboard tx: {}", txid);

offboard_vtxos

Offboard specific VTXOs to a Bitcoin address.
pub async fn offboard_vtxos<V: VtxoRef>(
    &self,
    vtxos: impl IntoIterator<Item = V>,
    address: bitcoin::Address,
) -> anyhow::Result<Txid>
vtxos
impl IntoIterator<Item = V>
required
VTXOs to offboard
address
bitcoin::Address
required
Destination Bitcoin address
Example:
// Select specific VTXOs to offboard
let vtxos = wallet.spendable_vtxos().await?;
let to_offboard = vtxos.into_iter().take(3).collect::<Vec<_>>();

let txid = wallet.offboard_vtxos(to_offboard, addr).await?;
Offboard fees are deducted from the total VTXO amount. The server calculates fees based on the output script and current fee rates.

Unilateral Exit (Non-Cooperative)

Unilateral exits allow you to claim funds onchain without server cooperation. The Exit subsystem is accessed through wallet.exit.

Access Exit Subsystem

// Get read lock
let exit = wallet.exit.read().await;

// Get write lock for mutations
let mut exit = wallet.exit.write().await;

Starting Exits

start_exit_for_entire_wallet

Mark all VTXOs for unilateral exit.
pub async fn start_exit_for_entire_wallet(&mut self) -> anyhow::Result<()>
Example:
let mut exit = wallet.exit.write().await;
exit.start_exit_for_entire_wallet().await?;

start_exit_for_vtxos

Mark specific VTXOs for unilateral exit.
pub async fn start_exit_for_vtxos(
    &mut self,
    vtxos: &[impl AsRef<Vtxo>]
) -> anyhow::Result<()>
vtxos
&[impl AsRef<Vtxo>]
required
VTXOs to exit
Example:
let vtxos = wallet.spendable_vtxos().await?;
let to_exit = vtxos.iter().take(2).collect::<Vec<_>>();

let mut exit = wallet.exit.write().await;
exit.start_exit_for_vtxos(&to_exit).await?;

Progressing Exits

sync_exits

Sync transaction status of unilateral exits.
pub async fn sync_exits(
    &self,
    onchain: &mut dyn ExitUnilaterally,
) -> anyhow::Result<()>
onchain
&mut dyn ExitUnilaterally
required
Onchain wallet for exit operations

progress_exits

Advance exit state machine (broadcast, fee-bump, etc.).
pub async fn progress_exits(
    &mut self,
    wallet: &Wallet,
    onchain: &mut impl ExitUnilaterally,
    fee_rate: Option<FeeRate>,
) -> anyhow::Result<()>
wallet
&Wallet
required
Reference to the Bark wallet
onchain
&mut impl ExitUnilaterally
required
Onchain wallet for signing transactions
fee_rate
Option<FeeRate>
Override fee rate (uses network estimate if None)
Example:
use bark::onchain::OnchainWallet;

let mut onchain = OnchainWallet::load_or_create(
    network, seed, db.clone()
).await?;

// Start exit
let mut exit_lock = wallet.exit.write().await;
exit_lock.start_exit_for_entire_wallet().await?;

// Progress periodically until complete
loop {
    exit_lock.sync_no_progress(&onchain).await?;
    exit_lock.progress_exits(&wallet, &mut onchain, None).await?;
    
    if !exit_lock.has_pending_exits() {
        break;
    }
    
    tokio::time::sleep(Duration::from_secs(60)).await;
}
Exit transactions may need to wait for the VTXO exit delta period before they can be broadcast. Continue calling progress_exits until completion.

Exit Status

get_exit_status

Get detailed status of a VTXO exit.
pub async fn get_exit_status(
    &self,
    vtxo_id: VtxoId,
    include_history: bool,
    include_transactions: bool,
) -> Result<Option<ExitTransactionStatus>, ExitError>
vtxo_id
VtxoId
required
VTXO ID to check
include_history
bool
required
Include state machine history
include_transactions
bool
required
Include transaction details
ExitTransactionStatus
struct
pub struct ExitTransactionStatus {
    pub vtxo_id: VtxoId,
    pub state: ExitState,
    pub history: Option<Vec<ExitState>>,
    pub transactions: Vec<ExitTransactionPackage>,
}
Example:
let exit = wallet.exit.read().await;
if let Some(status) = exit.get_exit_status(vtxo_id, true, true).await? {
    println!("State: {:?}", status.state);
    println!("Transactions: {}", status.transactions.len());
}

get_exit_vtxos

Get all VTXOs in the exit system.
pub fn get_exit_vtxos(&self) -> &Vec<ExitVtxo>
ExitVtxo
struct
pub struct ExitVtxo {
    // VTXO being exited
    // Current exit state
    // State history
    // Associated transactions
}

has_pending_exits

Check if any exits are in progress.
pub fn has_pending_exits(&self) -> bool
Example:
let exit = wallet.exit.read().await;
if exit.has_pending_exits() {
    println!("Exits still in progress");
}

pending_total

Get total amount in pending exits.
pub fn pending_total(&self) -> Amount

Claiming Exits

list_claimable

Get all exits that are ready to claim.
pub fn list_claimable(&self) -> Vec<VtxoId>
Returns
Vec<VtxoId>
VTXO IDs that are claimable (fully confirmed onchain)

drain_exits

Create a transaction to claim exited funds.
pub async fn drain_exits(
    &mut self,
    exit_vtxos: &[VtxoId],
    wallet: &Wallet,
    destination: Address,
    fee_rate: Option<FeeRate>,
) -> anyhow::Result<Psbt>
exit_vtxos
&[VtxoId]
required
VTXOs to claim (must be claimable)
wallet
&Wallet
required
Bark wallet reference
destination
Address
required
Where to send claimed funds
fee_rate
Option<FeeRate>
Override fee rate
Returns
Result<Psbt>
Signed PSBT ready to broadcast
Example:
let mut exit = wallet.exit.write().await;

// Get claimable exits
let claimable = exit.list_claimable();
if !claimable.is_empty() {
    let drain_addr = Address::from_str("bc1q...")?.assume_checked();
    
    let psbt = exit.drain_exits(
        &claimable,
        &wallet,
        drain_addr,
        None
    ).await?;
    
    // Broadcast the PSBT
    let tx = psbt.extract_tx()?;
    wallet.chain.broadcast_tx(&tx).await?;
}

Exit States

The exit state machine progresses through these states:
pub enum ExitState {
    Start(ExitStartState),
    Processing(ExitProcessingState),
    AwaitingDelta(ExitAwaitingDeltaState),
    Claimable(ExitClaimableState),
    ClaimInProgress(ExitClaimInProgressState),
    Claimed(ExitClaimedState),
}
State Descriptions:
  • Start: Exit initiated, not yet processed
  • Processing: Transaction created, awaiting broadcast
  • AwaitingDelta: Waiting for exit delta period to elapse
  • Claimable: Confirmed onchain, ready to claim
  • ClaimInProgress: Claim transaction broadcast
  • Claimed: Funds successfully claimed

Complete Exit Example

use bark::{Wallet, onchain::OnchainWallet};
use bitcoin::Address;
use std::time::Duration;

#[tokio::main]
async fn main() -> anyhow::Result<()> {
    // Load wallets
    let wallet = Wallet::open(&mnemonic, db.clone(), config).await?;
    let mut onchain = OnchainWallet::load_or_create(
        network, seed, db
    ).await?;
    
    // Start exit
    let mut exit = wallet.exit.write().await;
    exit.start_exit_for_entire_wallet().await?;
    drop(exit); // Release lock
    
    // Progress until complete
    loop {
        let mut exit = wallet.exit.write().await;
        exit.sync_no_progress(&onchain).await?;
        exit.progress_exits(&wallet, &mut onchain, None).await?;
        
        if !exit.has_pending_exits() {
            // All exits claimable
            let claimable = exit.list_claimable();
            let drain_addr = Address::from_str("bc1q...")?.assume_checked();
            
            let psbt = exit.drain_exits(
                &claimable,
                &wallet,
                drain_addr,
                None
            ).await?;
            
            let tx = psbt.extract_tx()?;
            wallet.chain.broadcast_tx(&tx).await?;
            
            println!("Exit complete: {}", tx.compute_txid());
            break;
        }
        drop(exit);
        
        tokio::time::sleep(Duration::from_secs(60)).await;
    }
    
    Ok(())
}

Comparison: Offboard vs Exit

FeatureOffboardUnilateral Exit
SpeedFast (minutes)Slow (hours/days)
CostLow feesHigher fees
ServerRequiredNot required
TrustServer must cooperateFully trustless
Use CaseNormal operationEmergency/dispute
Use offboarding when possible. Only use unilateral exits when the server is unavailable or uncooperative.

Build docs developers (and LLMs) love