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
Create invoice
Generate a BOLT11 invoice with bolt11_invoice().
Sender initiates payment
The sender pays your invoice over Lightning.
Server creates HTLCs
The Ark server receives the Lightning payment and creates HTLC VTXOs for you.
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
Select VTXOs
The wallet selects VTXOs to cover the payment amount plus fees.
Create HTLC VTXOs
An arkoor transaction creates HTLC VTXOs locked to the payment hash.
Register with server
HTLC VTXOs are registered with the server.
Initiate payment
The server forwards the payment over Lightning.
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
Regular syncing
Sync pending payments to update their status: wallet . sync_pending_lightning_send_vtxos () . await ? ;
wallet . try_claim_all_lightning_receives ( false ) . await ? ;
Handle timeouts
Lightning payments may take time. Use wait=false for polling: // Poll without blocking
let preimage = wallet . check_lightning_payment ( hash , false ) . await ? ;
Monitor HTLC expiry
HTLC VTXOs expire. Failed payments are automatically revoked, but sync regularly to free up funds.
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