Keyboard shortcuts

Press or to navigate between chapters

Press S or / to search in the book

Press ? to show this help

Press Esc to hide this help

Custom leaf optimization

The SDK implements a configurable Spark leaf optimization process. It supports two optimization policies:

  • Maximize unilateral exit efficiency: aims to minimize the number of leaves, reducing costs for unilaterally exiting Bitcoin funds.
  • Increase payment speed: maintains multiple copies of each leaf denomination to reduce the need for swaps during Bitcoin payments.

Configuring the optimization policy

The optimization behavior is controlled by the multiplicity setting, an integer value in the range 0-5. Setting it to 0 fully optimizes for unilateral exit efficiency, while values greater than 0 also optimize for payment speed. Higher values prioritize payment speed more aggressively, resulting in higher unilateral exit costs but faster payments, especially for bursts of transactions.

See Configuration to learn how to set the multiplicity.

Impact on payment speed

Multiplicity defines how many copies of each leaf denomination the SDK maintains. A higher multiplicity provides more flexibility in leaf combinations, reducing the frequency of swaps during payments. However, the exact number of swap-free payments depends on transaction amounts and patterns.

With automatic optimization, which is enabled by default, a multiplicity of 1 (the default) works well for most single-user applications with low payment frequency, eliminating the need for swaps in the vast majority of payment scenarios. Higher multiplicities are better suited for high-volume payment processing.

Impact on unilateral exit costs

Maintaining more leaves increases the total cost of unilaterally exiting funds, as each leaf incurs its own exit fee regardless of the leaf's value. This makes small denomination leaves cost-ineffective to exit.

Developer note

Keep multiplicity as low as possible while meeting your performance requirements. A high multiplicity can make unilateral exits prohibitively expensive.

Controlling optimization timing

The optimization process runs as a background task that reorganizes leaves by swapping them to achieve optimal denominations. During this process, funds in leaves being swapped become temporarily unavailable for payments, which can delay transaction processing.

By default, the SDK automatically triggers optimization after each payment (sent or received). For applications requiring more control, you can disable automatic optimization in the configuration and manage it manually as described below.

Start optimization API docs

You can manually trigger the optimization task to start running in the background. If optimization is already running, no new task will be started.

Rust
sdk.start_leaf_optimization();
Swift
sdk.startLeafOptimization()
Kotlin
sdk.startLeafOptimization()
C#
sdk.StartLeafOptimization();
Javascript
sdk.startLeafOptimization()
React Native
sdk.startLeafOptimization()
Flutter
sdk.startLeafOptimization();
Python
sdk.start_leaf_optimization()
Go
sdk.StartLeafOptimization()

Cancel optimization API docs

You can cancel an ongoing optimization task and wait for it to stop completely. Optimization is done in rounds, and the current round will complete before stopping.

Rust
sdk.cancel_leaf_optimization().await?;
Swift
do {
    try await sdk.cancelLeafOptimization()
} catch {
    print("Failed to cancel optimization: \(error)")
}
Kotlin
try {
    sdk.cancelLeafOptimization()
} catch (e: Exception) {
    // handle error
}
C#
await sdk.CancelLeafOptimization();
Javascript
await sdk.cancelLeafOptimization()
React Native
await sdk.cancelLeafOptimization()
Flutter
await sdk.cancelLeafOptimization();
Python
await sdk.cancel_leaf_optimization()
Go
err := sdk.CancelLeafOptimization()
if err != nil {
	return err
}

Developer note

The SDK automatically cancels optimization when it would block an immediate payment. Use manual cancellation only when anticipating upcoming payment activity, not when there is an immediate need to make a payment.

Get optimization progress API docs

You can retrieve the current optimization progress to monitor the optimization task.

Rust
let progress = sdk.get_leaf_optimization_progress();

info!("Optimization is running: {}", progress.is_running);
info!("Current round: {}", progress.current_round);
info!("Total rounds: {}", progress.total_rounds);
Swift
let progress = sdk.getLeafOptimizationProgress()

print("Optimization is running: \(progress.isRunning)")
print("Current round: \(progress.currentRound)")
print("Total rounds: \(progress.totalRounds)")
Kotlin
val progress = sdk.getLeafOptimizationProgress()

println("Optimization is running: ${progress.isRunning}")
println("Current round: ${progress.currentRound}")
println("Total rounds: ${progress.totalRounds}")
C#
var progress = sdk.GetLeafOptimizationProgress();

Console.WriteLine($"Optimization is running: {progress.isRunning}");
Console.WriteLine($"Current round: {progress.currentRound}");
Console.WriteLine($"Total rounds: {progress.totalRounds}");
Javascript
const progress = sdk.getLeafOptimizationProgress()

console.log(`Optimization is running: ${progress.isRunning}`)
console.log(`Current round: ${progress.currentRound}`)
console.log(`Total rounds: ${progress.totalRounds}`)
React Native
const progress = sdk.getLeafOptimizationProgress()

console.log(`Optimization is running: ${progress.isRunning}`)
console.log(`Current round: ${progress.currentRound}`)
console.log(`Total rounds: ${progress.totalRounds}`)
Flutter
var progress = sdk.getLeafOptimizationProgress();

print("Optimization is running: ${progress.isRunning}");
print("Current round: ${progress.currentRound}");
print("Total rounds: ${progress.totalRounds}");
Python
progress = sdk.get_leaf_optimization_progress()

logging.debug(f"Optimization is running: {progress.is_running}")
logging.debug(f"Current round: {progress.current_round}")
logging.debug(f"Total rounds: {progress.total_rounds}")
Go
progress := sdk.GetLeafOptimizationProgress()

log.Printf("Optimization is running: %v\n", progress.IsRunning)
log.Printf("Current round: %v\n", progress.CurrentRound)
log.Printf("Total rounds: %v\n", progress.TotalRounds)

Optimization events

The SDK emits events to keep your application informed about optimization status. See Listening to events for subscription instructions.

Rust
match event {
    OptimizationEvent::Started { total_rounds } => {
        info!("Optimization started with {} rounds", total_rounds);
    }
    OptimizationEvent::RoundCompleted { current_round, total_rounds } => {
        info!("Optimization round {} of {} completed", current_round, total_rounds);
    }
    OptimizationEvent::Completed => {
        info!("Optimization completed successfully");
    }
    OptimizationEvent::Cancelled => {
        info!("Optimization was cancelled");
    }
    OptimizationEvent::Failed { error } => {
        info!("Optimization failed: {}", error);
    }
    OptimizationEvent::Skipped => {
        info!("Optimization was skipped because leaves are already optimal");
    }
}
Swift
switch event {
    case .started(let totalRounds):
        print("Optimization started with \(totalRounds) rounds")
    case .roundCompleted(let currentRound, let totalRounds):
        print("Optimization round \(currentRound) of \(totalRounds) completed")
    case .completed:
        print("Optimization completed successfully")
    case .cancelled:
        print("Optimization was cancelled")
    case .failed(let error):
        print("Optimization failed: \(error)")
    case .skipped:
        print("Optimization was skipped because leaves are already optimal")
}
Kotlin
when (optimizationEvent) {
    is OptimizationEvent.Started -> {
        // Log.v("Breez", "Optimization started with ${optimizationEvent.totalRounds} rounds")
    }
    is OptimizationEvent.RoundCompleted -> {
        // Log.v("Breez", "Optimization round ${optimizationEvent.currentRound} of ${optimizationEvent.totalRounds} completed")
    }
    is OptimizationEvent.Completed -> {
        // Log.v("Breez", "Optimization completed successfully")
    }
    is OptimizationEvent.Cancelled -> {
        // Log.v("Breez", "Optimization was cancelled")
    }
    is OptimizationEvent.Failed -> {
        // Log.v("Breez", "Optimization failed: ${optimizationEvent.error}")
    }
    is OptimizationEvent.Skipped -> {
        // Log.v("Breez", "Optimization was skipped because leaves are already optimal")
    }
}
Javascript
switch (event.type) {
  case 'started': {
    console.log(`Optimization started with ${event.totalRounds} rounds`)
    break
  }
  case 'roundCompleted': {
    console.log(`Optimization round ${event.currentRound} of ${event.totalRounds} completed`)
    break
  }
  case 'completed': {
    console.log('Optimization completed successfully')
    break
  }
  case 'cancelled': {
    console.log('Optimization was cancelled')
    break
  }
  case 'failed': {
    console.log(`Optimization failed: ${event.error}`)
    break
  }
  case 'skipped': {
    console.log('Optimization was skipped because leaves are already optimal')
    break
  }
}
React Native
if (optimizationEvent.tag === OptimizationEvent_Tags.Started) {
  console.log(`Optimization started with ${optimizationEvent.inner.totalRounds} rounds`)
} else if (optimizationEvent.tag === OptimizationEvent_Tags.RoundCompleted) {
  console.log(`Optimization round ${optimizationEvent.inner.currentRound} of ${optimizationEvent.inner.totalRounds} completed`)
} else if (optimizationEvent.tag === OptimizationEvent_Tags.Completed) {
  console.log('Optimization completed successfully')
} else if (optimizationEvent.tag === OptimizationEvent_Tags.Cancelled) {
  console.log('Optimization was cancelled')
} else if (optimizationEvent.tag === OptimizationEvent_Tags.Failed) {
  console.log(`Optimization failed: ${optimizationEvent.inner.error}`)
} else if (optimizationEvent.tag === OptimizationEvent_Tags.Skipped) {
  console.log('Optimization was skipped because leaves are already optimal')
}
Python
if isinstance(optimization_event, OptimizationEvent.STARTED):
    logging.debug(f"Optimization started with {optimization_event.total_rounds} rounds")
elif isinstance(optimization_event, OptimizationEvent.ROUND_COMPLETED):
    logging.debug(f"Optimization round {optimization_event.current_round} of "
        f"{optimization_event.total_rounds} completed")
elif isinstance(optimization_event, OptimizationEvent.COMPLETED):
    logging.debug("Optimization completed successfully")
elif isinstance(optimization_event, OptimizationEvent.CANCELLED):
    logging.debug("Optimization was cancelled")
elif isinstance(optimization_event, OptimizationEvent.FAILED):
    logging.debug(f"Optimization failed: {optimization_event.error}")
elif isinstance(optimization_event, OptimizationEvent.SKIPPED):
    logging.debug("Optimization was skipped because leaves are already optimal")
Go
switch event := optimizationEvent.(type) {
case breez_sdk_spark.OptimizationEventStarted:
	log.Printf("Optimization started with %v rounds\n", event.TotalRounds)
case breez_sdk_spark.OptimizationEventRoundCompleted:
	log.Printf("Optimization round %v of %v completed\n", event.CurrentRound, event.TotalRounds)
case breez_sdk_spark.OptimizationEventCompleted:
	log.Printf("Optimization completed successfully\n")
case breez_sdk_spark.OptimizationEventCancelled:
	log.Printf("Optimization was cancelled\n")
case breez_sdk_spark.OptimizationEventFailed:
	log.Printf("Optimization failed: %v\n", event.Error)
case breez_sdk_spark.OptimizationEventSkipped:
	log.Printf("Optimization was skipped because leaves are already optimal\n")
}