Skip to main content

Overview

Bark seamlessly integrates with the Lightning Network, allowing you to:
  • Send: Pay BOLT11 and BOLT12 invoices, Lightning addresses, and offers
  • Receive: Generate invoices and receive payments over Lightning
All Lightning operations use your offchain Ark balance—no channel management required.

Receiving Lightning Payments

Generate a BOLT11 Invoice

Create an invoice for a specific amount:
use bitcoin::Amount;
use lightning_invoice::Bolt11Invoice;

#[tokio::main]
async fn main() -> anyhow::Result<()> {
    // Create a 10,000 sat invoice
    let amount = Amount::from_sat(10_000);
    let invoice = wallet.bolt11_invoice(amount).await?;

    println!("Share this invoice with the sender:");
    println!("{}", invoice);

    Ok(())
}
Bark does not currently support zero-amount (“any amount”) invoices. You must specify an amount.

Invoice Validation

Invoices include automatic CLTV delta calculations to ensure safe receipt:
let ark_info = wallet.ark_info().await?.expect("connected");

// The invoice CLTV delta ensures enough time to:
// - Exit the VTXO before HTLC expiry (vtxo_exit_delta)
// - Account for HTLC expiry (htlc_expiry_delta)
// - Add safety margins (vtxo_exit_margin, htlc_recv_claim_delta)

let config = wallet.config();
let min_cltv = ark_info.vtxo_exit_delta +
    ark_info.htlc_expiry_delta +
    config.vtxo_exit_margin +
    config.htlc_recv_claim_delta;

println!("Minimum CLTV delta: {}", min_cltv);

Claim a Lightning Receive

After the invoice is paid, claim the payment:
use ark::lightning::PaymentHash;

// Get payment hash from your invoice
let payment_hash: PaymentHash = invoice.into();

// Wait for and claim the payment
let receive = wallet.try_claim_lightning_receive(
    payment_hash,
    true, // wait for payment
    None  // no token needed if you have spendable VTXOs
).await?;

println!("Payment claimed!");
println!("Amount: {}", receive.invoice.amount_milli_satoshis().unwrap() / 1000);

Claim All Pending Receives

Claim all pending Lightning receives at once:
// Claim all pending receives
wallet.try_claim_all_lightning_receives(false).await?;

// Or wait for each to complete
wallet.try_claim_all_lightning_receives(true).await?;

Track Pending Receives

use bark::persist::models::LightningReceive;

// Get all pending receives
let pending = wallet.pending_lightning_receives().await?;

for receive in pending {
    println!("Payment hash: {}", receive.payment_hash);
    println!("Invoice: {}", receive.invoice);
    println!("HTLC VTXOs: {}", receive.htlc_vtxos.len());
    
    if let Some(movement_id) = receive.movement_id {
        println!("Movement ID: {}", movement_id);
    }
}

// Check claimable balance
let claimable = wallet.claimable_lightning_receive_balance().await?;
println!("Claimable: {}", claimable);

Lightning Receive Lifecycle

1

Create invoice

Generate a BOLT11 invoice with bolt11_invoice().
2

Sender initiates payment

The sender pays your invoice over Lightning.
3

Server creates HTLCs

The Ark server receives the Lightning payment and creates HTLC VTXOs for you.
4

Claim payment

You reveal the preimage to claim the HTLC VTXOs, converting them to regular spendable VTXOs.

Sending Lightning Payments

Pay a BOLT11 Invoice

use lightning_invoice::Bolt11Invoice;

let invoice = Bolt11Invoice::from_str("lnbc...")?
    .into();

// Pay the invoice
let payment = wallet.pay_lightning_invoice(
    invoice,
    None // use invoice amount
).await?;

println!("Payment sent!");
println!("Payment hash: {}", payment.invoice.payment_hash());

Pay with Custom Amount

Override the invoice amount (for zero-amount invoices):
let amount = Amount::from_sat(5_000);
let payment = wallet.pay_lightning_invoice(invoice, Some(amount)).await?;

Pay a BOLT12 Offer

use ark::lightning::Offer;

let offer = Offer::from_str("lno...")?.into();

// Pay the offer
let payment = wallet.pay_lightning_offer(
    offer,
    None // use offer amount
).await?;

println!("Offer paid: {:?}", payment);

Pay a Lightning Address

use lnurllib::lightning_address::LightningAddress;

let address = LightningAddress::from_str("[email protected]")?;
let amount = Amount::from_sat(10_000);

// Pay with optional comment
let payment = wallet.pay_lightning_address(
    &address,
    amount,
    Some("Thanks!") // optional comment
).await?;

println!("Paid {} to {}", amount, address);

Check Payment Status

Monitor the status of a Lightning payment:
use ark::lightning::Preimage;

let payment_hash = payment.invoice.payment_hash();

// Check if payment completed
let preimage: Option<Preimage> = wallet.check_lightning_payment(
    payment_hash,
    true // wait for completion
).await?;

if let Some(preimage) = preimage {
    println!("Payment successful! Preimage: {}", preimage);
} else {
    println!("Payment still pending or failed");
}

Track Pending Sends

use bark::persist::models::LightningSend;

// Get all pending payments
let pending = wallet.pending_lightning_sends().await?;

for payment in pending {
    println!("Invoice: {}", payment.invoice);
    println!("Amount: {}", payment.amount);
    println!("Fee: {}", payment.fee);
    println!("HTLC VTXOs: {}", payment.htlc_vtxos.len());
    
    if let Some(preimage) = payment.preimage {
        println!("Status: Completed (preimage: {})", preimage);
    } else {
        println!("Status: Pending");
    }
}

Sync Pending Payments

Check the status of all pending Lightning payments:
// Sync pending sends (checks status, revokes failed HTLCs)
wallet.sync_pending_lightning_send_vtxos().await?;

Lightning Payment Process

Sending

1

Select VTXOs

The wallet selects VTXOs to cover the payment amount plus fees.
2

Create HTLC VTXOs

An arkoor transaction creates HTLC VTXOs locked to the payment hash.
3

Register with server

HTLC VTXOs are registered with the server.
4

Initiate payment

The server forwards the payment over Lightning.
5

Settle or revoke

  • If successful: Server provides preimage, HTLCs are marked as spent
  • If failed: HTLCs are revoked, returning funds to your balance

Revocation

Failed payments are automatically revoked:
// Revocation happens automatically during sync
wallet.sync_pending_lightning_send_vtxos().await?;

// You can also check manually
for payment in wallet.pending_lightning_sends().await? {
    let hash = payment.invoice.payment_hash();
    if let None = wallet.check_lightning_payment(hash, false).await? {
        println!("Payment {} may be failed or pending", hash);
    }
}

Lightning Fees

Receive Fees

let ark_info = wallet.ark_info().await?.expect("connected");
let amount = Amount::from_sat(10_000);

// Calculate receive fee
let fee = ark_info.fees.lightning_receive.calculate(amount)?;
println!("Receiving {} will cost {} in fees", amount, fee);
println!("Net amount: {}", amount - fee);

Send Fees

let ark_info = wallet.ark_info().await?.expect("connected");
let amount = Amount::from_sat(10_000);

// Fee depends on number of VTXOs being spent
let vtxos = wallet.select_vtxos_to_cover(amount).await?;
let fee = ark_info.fees.lightning_send.calculate(amount, vtxos.len())?;

println!("Sending {} will cost {} in fees", amount, fee);
println!("Total: {}", amount + fee);

Complete Lightning Workflow

Receiving Example

use bark::{Config, Wallet, SqliteClient};
use bitcoin::Amount;
use std::sync::Arc;

#[tokio::main]
async fn main() -> anyhow::Result<()> {
    // Setup wallet
    let wallet = Wallet::open(
        &mnemonic,
        Arc::new(SqliteClient::open("db.sqlite").await?),
        Config::network_default(bitcoin::Network::Signet)
    ).await?;

    // Create invoice
    let amount = Amount::from_sat(10_000);
    let invoice = wallet.bolt11_invoice(amount).await?;
    println!("Share this invoice: {}", invoice);

    // Wait for payment
    let payment_hash = invoice.into();
    loop {
        match wallet.try_claim_lightning_receive(payment_hash, false, None).await {
            Ok(receive) if receive.finished_at.is_some() => {
                println!("Payment received and claimed!");
                break;
            }
            Ok(_) => {
                println!("Waiting for payment...");
            }
            Err(e) => {
                println!("Error: {:#}", e);
                break;
            }
        }
        tokio::time::sleep(tokio::time::Duration::from_secs(5)).await;
    }

    // Check balance
    let balance = wallet.balance().await?;
    println!("New balance: {}", balance.spendable);

    Ok(())
}

Sending Example

use lightning_invoice::Bolt11Invoice;

#[tokio::main]
async fn main() -> anyhow::Result<()> {
    let wallet = /* ... */;

    // Parse invoice
    let invoice: Bolt11Invoice = "lnbc...".parse()?;
    
    // Check balance
    let balance = wallet.balance().await?;
    let amount = invoice.amount_milli_satoshis()
        .map(|m| Amount::from_sat(m / 1000))
        .unwrap_or(Amount::ZERO);
    
    if balance.spendable < amount {
        return Err(anyhow!("Insufficient balance"));
    }

    // Send payment
    let payment = wallet.pay_lightning_invoice(invoice.clone(), None).await?;
    println!("Payment initiated: {}", payment.invoice.payment_hash());

    // Wait for completion
    let hash = payment.invoice.payment_hash();
    loop {
        if let Some(preimage) = wallet.check_lightning_payment(hash, false).await? {
            println!("Payment successful! Preimage: {}", preimage);
            break;
        }
        
        tokio::time::sleep(tokio::time::Duration::from_secs(2)).await;
    }

    Ok(())
}

Error Handling

// Sending errors
match wallet.pay_lightning_invoice(invoice, None).await {
    Ok(payment) => println!("Payment sent"),
    Err(e) if e.to_string().contains("already been paid") => {
        println!("Invoice already paid");
    }
    Err(e) if e.to_string().contains("wrong network") => {
        println!("Invoice is for wrong network");
    }
    Err(e) if e.to_string().contains("enough suitable VTXOs") => {
        println!("Insufficient balance");
    }
    Err(e) => return Err(e),
}

// Receiving errors
match wallet.bolt11_invoice(amount).await {
    Ok(invoice) => println!("Invoice: {}", invoice),
    Err(e) if e.to_string().contains("Cannot create invoice for 0") => {
        println!("Zero-amount invoices not supported");
    }
    Err(e) => return Err(e),
}

Best Practices

1

Regular syncing

Sync pending payments to update their status:
wallet.sync_pending_lightning_send_vtxos().await?;
wallet.try_claim_all_lightning_receives(false).await?;
2

Handle timeouts

Lightning payments may take time. Use wait=false for polling:
// Poll without blocking
let preimage = wallet.check_lightning_payment(hash, false).await?;
3

Monitor HTLC expiry

HTLC VTXOs expire. Failed payments are automatically revoked, but sync regularly to free up funds.
4

Account for fees

Always check fees before sending:
let ark_info = wallet.ark_info().await?.unwrap();
let fee = ark_info.fees.lightning_send.calculate(amount, vtxos.len())?;
If a Lightning receive fails after you’ve revealed the preimage, the HTLCs will be automatically exited to onchain. This is rare but ensures you don’t lose funds.

Next Steps

Exits and Claims

Learn about unilateral exits and claiming funds

Sending Payments

Send Arkoor and onchain payments

Build docs developers (and LLMs) love