Skip to main content

What is a VTXO?

A Virtual Transaction Output (VTXO) represents your ownership of funds within the Ark. Unlike a regular Bitcoin UTXO that exists directly on-chain, a VTXO is an off-chain construct backed by a chain of pre-signed transactions that can be broadcast to claim your funds.
Think of a VTXO as a “promise” of on-chain Bitcoin that you can either spend off-chain (cooperatively) or claim on-chain (unilaterally) at any time.

VTXO Structure

The Vtxo struct is defined in lib/src/vtxo/mod.rs:
pub struct Vtxo<P = VtxoPolicy> {
    policy: P,                    // Spending conditions
    amount: Amount,               // Value in satoshis
    expiry_height: BlockHeight,   // When VTXO expires
    server_pubkey: PublicKey,     // Server's public key
    exit_delta: BlockDelta,       // Timelock for exits
    anchor_point: OutPoint,       // On-chain anchor UTXO
    genesis: Vec<GenesisItem>,    // Exit transaction chain
    point: OutPoint,              // VTXO identifier
}

Key Components

VTXO ID
  • 36-byte identifier: [txid: 32 bytes][vout: 4 bytes]
  • Uniquely identifies each VTXO
  • Encoded as VtxoId type
Amount
  • Denominated in satoshis
  • Subject to dust limits (currently 330 sats for P2TR)
  • May have maximum limits set by server (max_vtxo_amount)
Chain Anchor
  • Reference to the on-chain UTXO that anchors this VTXO
  • Must be confirmed for the VTXO to be valid
  • Can be a round transaction output or board transaction output
Exit Depth
  • Number of transactions required for unilateral exit
  • Calculated as: genesis.len()
  • Typical depths: 1 (board), 3 (round), 3+ (arkoor with history)

VTXO Policies

VTXOs can have different spending policies based on their purpose:

Policy Types

1. Pubkey (Standard VTXO)
VtxoPolicy::Pubkey(PubkeyVtxoPolicy {
    user_pubkey: PublicKey,
})
  • Standard user-owned VTXO
  • Can be spent cooperatively with server
  • Can be exited unilaterally after exit_delta
2. Server HTLC Send
VtxoPolicy::ServerHtlcSend(ServerHtlcSendVtxoPolicy {
    user_pubkey: PublicKey,
    payment_hash: PaymentHash,
    htlc_expiry: BlockHeight,
})
  • Used when sending Lightning payments
  • Locked until payment preimage revealed
  • Automatically exits if near expiry without settlement
3. Server HTLC Receive
VtxoPolicy::ServerHtlcRecv(ServerHtlcRecvVtxoPolicy {
    user_pubkey: PublicKey,
    payment_hash: PaymentHash,
    htlc_expiry: BlockHeight,
    htlc_expiry_delta: BlockDelta,
})
  • Used when receiving Lightning payments
  • User can claim with preimage
  • Falls back to exit if preimage not revealed in time

Server-Only Policies

These policies are used internally by the server:
  • Checkpoint: Intermediate outputs in arkoor transaction chains
  • Expiry: VTXOs that have expired and can be reclaimed by server
  • HarkLeaf: Hash-locked arkoor leaf outputs for atomic swaps

VTXO Lifecycle

1. Creation

VTXOs are created during:
  • Boarding: Converting on-chain funds to Ark
  • Rounds: Refreshing existing VTXOs or receiving payments
  • Arkoor transfers: Receiving off-chain payments
  • Lightning receives: Settling incoming Lightning payments

2. Active Use

While active, VTXOs can be:
  • Spent in arkoor transfers
  • Combined with other VTXOs for payments
  • Used to pay Lightning invoices
  • Offboarded cooperatively
  • Refreshed to extend lifetime

3. Expiry

All VTXOs have an expiry_height:
VTXOs that are not refreshed before expiry can be claimed by the server. Users must monitor expiry heights and refresh VTXOs periodically.
Typical expiry timeline:
Current Height: 800,000
VTXO Expiry:    804,032  (+ vtxo_expiry_delta)

4. Exit

VTXOs can be exited:
  • Cooperatively: Via offboard in a round (preferred)
  • Unilaterally: By broadcasting exit transactions (emergency)

Genesis Items and Exit Transactions

Each VTXO contains a genesis vector of GenesisItem structures that encode the exit transaction chain:
pub struct GenesisItem {
    transition: GenesisTransition,  // How to spend this level
    output_idx: u8,                 // Which output continues the chain
    other_outputs: Vec<TxOut>,      // Sibling outputs in tree
    fee_amount: Amount,             // Fee for this transaction
}

Exit Transaction Chain

When you exit a VTXO unilaterally, you must broadcast a chain of transactions:
  1. First transaction: Spends the chain anchor (on-chain UTXO)
  2. Intermediate transactions: Navigate down the tree structure
  3. Final output: Your VTXO output with your pubkey
Example for a round VTXO (depth 3):
Round TX Output (on-chain)
  ↓ [Genesis Item 0: Cosigned transition]
Tree Level 1 Output
  ↓ [Genesis Item 1: Cosigned transition]
Tree Level 2 Output
  ↓ [Genesis Item 2: Cosigned transition]
Your VTXO Output

Genesis Transitions

Three types of transitions connect exit transactions: 1. Cosigned
GenesisTransition::Cosigned {
    pubkeys: Vec<PublicKey>,         // User+server pubkeys
    signature: Option<Signature>,     // Aggregated signature
}
  • Used for round VTXOs and board VTXOs
  • Requires MuSig2 aggregated signature
  • Most common transition type
2. Arkoor
GenesisTransition::Arkoor {
    client_cosigners: Vec<PublicKey>,
    tap_tweak: TapTweakHash,
    signature: Option<Signature>,
}
  • Used for arkoor transfer outputs
  • Includes past cosigners for audit trail
  • Enables multi-hop arkoor chains
3. Hash-Locked Cosigned
GenesisTransition::HashLockedCosigned {
    user_pubkey: PublicKey,
    signature: Option<Signature>,
    unlock: MaybePreimage,  // Preimage or hash
}
  • Used for hArk (hash-locked arkoor) leaf outputs
  • Enables atomic swaps
  • Preimage revealed to complete payment

VTXO Validation

Before accepting a VTXO, clients validate:
  1. Chain anchor exists on-chain and has required confirmations
  2. All signatures are valid in the exit transaction chain
  3. All outputs are standard for relay purposes
  4. Fee anchors are present to enable CPFP fee-bumping
  5. Expiry height is reasonable and not already passed
  6. Amount calculations are correct throughout the chain
vtxo.validate(&chain_anchor_tx)?;
VTXO validation is critical for security. Never accept VTXOs without validating them against the confirmed chain anchor transaction.

Transaction Weight and Fees

Each exit transaction in the chain has a fixed weight:
pub const EXIT_TX_WEIGHT: Weight = Weight::from_vb_unchecked(124);
For unilateral exits:
  • Each transaction includes a fee anchor output
  • Users can CPFP (Child-Pays-For-Parent) to bump fees
  • Total cost = exit_depth × EXIT_TX_WEIGHT × fee_rate

VTXO Encoding

VTXOs are encoded using ProtocolEncoding for efficient serialization:
  • Version: 2 (current), with backward compatibility for version 1
  • Size: Variable, typically 500-2000 bytes depending on depth
  • Format: Binary encoding optimized for network transmission
Encoding format:
[version: 2 bytes]
[amount: 8 bytes]
[expiry_height: 4 bytes]
[server_pubkey: 33 bytes]
[exit_delta: 2 bytes]
[anchor_point: 36 bytes]
[genesis_items: variable]
[policy: variable]
[point: 36 bytes]

Working with VTXOs

Iterating Exit Transactions

for tx_item in vtxo.transactions() {
    println!("TX: {}", tx_item.tx.compute_txid());
    println!("Output index: {}", tx_item.output_idx);
}

Checking Expiry

let current_height = 800_000;
let blocks_until_expiry = vtxo.expiry_height().saturating_sub(current_height);

if blocks_until_expiry < 144 {
    println!("Warning: VTXO expires in {} blocks!", blocks_until_expiry);
}

Calculating Exit Depth

let depth = vtxo.exit_depth();
println!("Exit requires {} transactions", depth);

Best Practices

DO:
  • Monitor VTXO expiry heights
  • Validate all received VTXOs
  • Refresh VTXOs well before expiry
  • Keep exit transactions ready for emergencies
  • Understand the exit depth for fee planning
DON’T:
  • Accept VTXOs without validation
  • Let VTXOs expire unrefreshed
  • Assume the server will always cooperate
  • Ignore exit transaction fee requirements
  • Create VTXOs below the dust limit

Further Reading

Rounds

How VTXOs are created and refreshed

Exits

Claiming VTXOs on-chain

Build docs developers (and LLMs) love