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

Handling unclaimed deposits

When receiving Bitcoin payments through onchain deposits, the SDK automatically attempts to claim these funds to make them available in your wallet. However, there are scenarios where the deposit claiming process may fail, requiring manual intervention to either retry the claim or refund the deposit to an external Bitcoin address.

Understanding why deposits are unclaimed

Unclaimed deposits can happen for several reasons:

  • Insufficient fee configuration: The maximum configured fees may be not set or too low to process the claim transaction during periods of high network congestion
  • UTXO unavailability: The deposit UTXO may no longer be available or has been spent elsewhere
  • Other unexpected errors: Various technical issues that prevent successful claiming

The SDK emits a UnclaimedDeposits event containing information about the unclaimed deposits, including the specific reason why the deposit is unclaimed.

Managing unclaimed deposits

The SDK provides three methods to handle unclaimed deposits:

  1. Listing unclaimed deposits - Retrieve all deposits that have not yet been claimed
  2. Claiming a deposit - Claim a deposit using specific claiming parameters
  3. Refunding a deposit - Send the deposit funds to an external Bitcoin address

Listing unclaimed deposits

This lists all of the currently unclaimed deposits, including the specific reason why the deposit is unclaimed.

Rust
let request = ListUnclaimedDepositsRequest {};
let response = sdk.list_unclaimed_deposits(request).await?;

for deposit in response.deposits {
    info!("Unclaimed deposit: {}:{}", deposit.txid, deposit.vout);
    info!("Amount: {} sats", deposit.amount_sats);

    if let Some(claim_error) = &deposit.claim_error {
        match claim_error {
            DepositClaimError::DepositClaimFeeExceeded { max_fee, actual_fee, .. } => {
                info!("Max claim fee exceeded. Max: {:?}, Actual: {} sats", max_fee, actual_fee);
            }
            DepositClaimError::MissingUtxo { .. } => {
                info!("UTXO not found when claiming deposit");
            }
            DepositClaimError::Generic { message } => {
                info!("Claim failed: {}", message);
            }
        }
    }
}
Swift
let request = ListUnclaimedDepositsRequest()
let response = try await sdk.listUnclaimedDeposits(request: request)

for deposit in response.deposits {
    print("Unclaimed deposit: \(deposit.txid):\(deposit.vout)")
    print("Amount: \(deposit.amountSats) sats")

    if let claimError = deposit.claimError {
        switch claimError {
        case .depositClaimFeeExceeded(let tx, let vout, let maxFee, let actualFee):
            print("Max claim fee exceeded. Max: \(maxFee), Actual: \(actualFee) sats")
        case .missingUtxo(let tx, let vout):
            print("UTXO not found when claiming deposit")
        case .generic(let message):
            print("Claim failed: \(message)")
        }
    }
}
Kotlin
try {
    val request = ListUnclaimedDepositsRequest
    val response = sdk.listUnclaimedDeposits(request)

    for (deposit in response.deposits) {
        // Log.v("Breez", "Unclaimed deposit: ${deposit.txid}:${deposit.vout}")
        // Log.v("Breez", "Amount: ${deposit.amountSats} sats")

        deposit.claimError?.let { claimError ->
            when (claimError) {
                is DepositClaimError.DepositClaimFeeExceeded -> {
                    // Log.v("Breez", "Max claim fee exceeded. Max: ${claimError.maxFee}, Actual: ${claimError.actualFee} sats")
                }
                is DepositClaimError.MissingUtxo -> {
                    // Log.v("Breez", "UTXO not found when claiming deposit")
                }
                is DepositClaimError.Generic -> {
                    // Log.v("Breez", "Claim failed: ${claimError.message}")
                }
            }
        }
    }
} catch (e: Exception) {
    // handle error
}
C#
var request = new ListUnclaimedDepositsRequest();
var response = await sdk.ListUnclaimedDeposits(request: request);

foreach (var deposit in response.deposits)
{
    Console.WriteLine($"Unclaimed deposit: {deposit.txid}:{deposit.vout}");
    Console.WriteLine($"Amount: {deposit.amountSats} sats");

    if (deposit.claimError != null)
    {
        if (deposit.claimError is DepositClaimError.DepositClaimFeeExceeded exceeded)
        {
            Console.WriteLine($"Claim failed: Fee exceeded. Max: {exceeded.maxFee}, " +
                            $"Actual: {exceeded.actualFee}");
        }
        else if (deposit.claimError is DepositClaimError.MissingUtxo)
        {
            Console.WriteLine("Claim failed: UTXO not found");
        }
        else if (deposit.claimError is DepositClaimError.Generic generic)
        {
            Console.WriteLine($"Claim failed: {generic.message}");
        }
    }
}
Javascript
const request: ListUnclaimedDepositsRequest = {}
const response = await sdk.listUnclaimedDeposits(request)

for (const deposit of response.deposits) {
  console.log(`Unclaimed deposit: ${deposit.txid}:${deposit.vout}`)
  console.log(`Amount: ${deposit.amountSats} sats`)

  if (deposit.claimError != null) {
    switch (deposit.claimError.type) {
      case 'depositClaimFeeExceeded': {
        let maxFeeStr = 'none'
        if (deposit.claimError.maxFee != null) {
          maxFeeStr = deposit.claimError.maxFee.type === 'fixed'
            ? `${deposit.claimError.maxFee.amount} sats`
            : `${deposit.claimError.maxFee.satPerVbyte} sat/vB`
        }
        console.log(
          `Max claim fee exceeded. Max: ${maxFeeStr}, Actual: ${deposit.claimError.actualFee} sats`
        )
        break
      }
      case 'missingUtxo':
        console.log('UTXO not found when claiming deposit')
        break
      case 'generic':
        console.log(`Claim failed: ${deposit.claimError.message}`)
        break
    }
  }
}
React Native
const request: ListUnclaimedDepositsRequest = {}
const response = await sdk.listUnclaimedDeposits(request)

for (const deposit of response.deposits) {
  console.log(`Unclaimed deposit: ${deposit.txid}:${deposit.vout}`)
  console.log(`Amount: ${deposit.amountSats} sats`)

  if (deposit.claimError != null) {
    if (deposit.claimError instanceof DepositClaimError.DepositClaimFeeExceeded) {
      let maxFeeStr = 'none'
      if (deposit.claimError.inner.maxFee instanceof Fee.Fixed) {
        maxFeeStr = `${deposit.claimError.inner.maxFee.inner.amount} sats`
      } else if (deposit.claimError.inner.maxFee instanceof Fee.Rate) {
        maxFeeStr = `${deposit.claimError.inner.maxFee.inner.satPerVbyte} sat/vB`
      }
      console.log(
        `Max claim fee exceeded. Max: ${maxFeeStr}, Actual: ${deposit.claimError.inner.actualFee} sats`
      )
    } else if (deposit.claimError instanceof DepositClaimError.MissingUtxo) {
      console.log('UTXO not found when claiming deposit')
    } else if (deposit.claimError instanceof DepositClaimError.Generic) {
      console.log(`Claim failed: ${deposit.claimError.inner.message}`)
    }
  }
}
Flutter
final request = ListUnclaimedDepositsRequest();
final response = await sdk.listUnclaimedDeposits(request: request);

for (DepositInfo deposit in response.deposits) {
  print("Unclaimed deposit: ${deposit.txid}:${deposit.vout}");
  print("Amount: ${deposit.amountSats} sats");

  final claimError = deposit.claimError;
  if (claimError is DepositClaimError_DepositClaimFeeExceeded) {
    print(
        "Max claim fee exceeded. Max: ${claimError.maxFee}, Actual: ${claimError.actualFee} sats");
  } else if (claimError is DepositClaimError_MissingUtxo) {
    print("UTXO not found when claiming deposit");
  } else if (claimError is DepositClaimError_Generic) {
    print("Claim failed: ${claimError.message}");
  }
}
Python
try:
    request = ListUnclaimedDepositsRequest()
    response = await sdk.list_unclaimed_deposits(request=request)

    for deposit in response.deposits:
        logging.info(f"Unclaimed deposit: {deposit.txid}:{deposit.vout}")
        logging.info(f"Amount: {deposit.amount_sats} sats")

        if deposit.claim_error:
            if isinstance(
                deposit.claim_error, DepositClaimError.DEPOSIT_CLAIM_FEE_EXCEEDED
            ):
                logging.info(
                    f"Claim failed: Fee exceeded. Max: {deposit.claim_error.max_fee}, "
                    f"Actual: {deposit.claim_error.actual_fee}"
                )
            elif isinstance(deposit.claim_error, DepositClaimError.MISSING_UTXO):
                logging.info("Claim failed: UTXO not found")
            elif isinstance(deposit.claim_error, DepositClaimError.GENERIC):
                logging.info(f"Claim failed: {deposit.claim_error.message}")
except Exception as error:
    logging.error(error)
    raise
Go
request := breez_sdk_spark.ListUnclaimedDepositsRequest{}
response, err := sdk.ListUnclaimedDeposits(request)

if sdkErr := err.(*breez_sdk_spark.SdkError); sdkErr != nil {
	return err
}

for _, deposit := range response.Deposits {
	log.Printf("Unclaimed Deposit: %v:%v", deposit.Txid, deposit.Vout)
	log.Printf("Amount: %v sats", deposit.AmountSats)

	if claimErr := *deposit.ClaimError; claimErr != nil {
		switch claimErr := claimErr.(type) {
		case breez_sdk_spark.DepositClaimErrorDepositClaimFeeExceeded:
			log.Printf("Max claim fee exceeded. Max: %v, Actual: %v sats", claimErr.MaxFee, claimErr.ActualFee)
		case breez_sdk_spark.DepositClaimErrorMissingUtxo:
			log.Print("UTXO not found when claiming deposit")
		case breez_sdk_spark.DepositClaimErrorGeneric:
			log.Printf("Claim failed: %v", claimErr.Message)
		case nil:
			log.Printf("No claim error")
		}
	}
}

Claiming a deposit

If a deposit is unclaimed due to insufficient fees, you can retry the claim operation with a higher maximum fee. This is particularly useful during periods of high network congestion when transaction fees are elevated.

Rust
let txid = "your_deposit_txid".to_string();
let vout = 0;

// Set a higher max fee to retry claiming
let max_fee = Some(Fee::Fixed { amount: 500 });

let request = ClaimDepositRequest {
    txid,
    vout,
    max_fee,
};

let response = sdk.claim_deposit(request).await?;
info!("Deposit claimed successfully. Payment: {:?}", response.payment);
Swift
let txid = "your_deposit_txid"
let vout: UInt32 = 0

// Set a higher max fee to retry claiming
let maxFee = Fee.fixed(amount: 5000) // 5000 sats

let request = ClaimDepositRequest(
    txid: txid,
    vout: vout,
    maxFee: maxFee
)

let response = try await sdk.claimDeposit(request: request)
print("Deposit claimed successfully. Payment: \(response.payment)")
Kotlin
try {
    val txid = "your_deposit_txid"
    val vout = 0u

    // Set a higher max fee to retry claiming
    val maxFee = Fee.Fixed(5000u)

    val request = ClaimDepositRequest(
        txid = txid,
        vout = vout,
        maxFee = maxFee
    )

    val response = sdk.claimDeposit(request)
    // Log.v("Breez", "Deposit claimed successfully. Payment: ${response.payment}")
} catch (e: Exception) {
    // handle error
}
C#
var txid = "your_deposit_txid";
var vout = 0U;

// Set a higher max fee to retry claiming
var maxFee = new Fee.Fixed(amount: 5_000);

var request = new ClaimDepositRequest(txid: txid, vout: vout, maxFee: maxFee);

var response = await sdk.ClaimDeposit(request: request);
Console.WriteLine($"Deposit claimed successfully. Payment: {response.payment}");
Javascript
const txid = 'your_deposit_txid'
const vout = 0

// Set a higher max fee to retry claiming
const maxFee: Fee = {
  type: 'fixed',
  amount: 5_000
}

const request: ClaimDepositRequest = {
  txid,
  vout,
  maxFee
}

const response = await sdk.claimDeposit(request)
console.log('Deposit claimed successfully. Payment:', response.payment)
React Native
const txid = 'your_deposit_txid'
const vout = 0

// Set a higher max fee to retry claiming
const maxFee = new Fee.Fixed({ amount: BigInt(5000) })

const request: ClaimDepositRequest = {
  txid,
  vout,
  maxFee
}

const response = await sdk.claimDeposit(request)
console.log('Deposit claimed successfully. Payment:', response.payment)
Flutter
String txid = "your_deposit_txid";
int vout = 0;

Fee maxFee = Fee.fixed(amount: BigInt.from(5000));
final request = ClaimDepositRequest(
  txid: txid,
  vout: vout,
  maxFee: maxFee,
);

final response = await sdk.claimDeposit(request: request);
print("Deposit claimed successfully. Payment: ${response.payment}");
Python
try:
    txid = "your_deposit_txid"
    vout = 0

    # Set a higher max fee to retry claiming
    max_fee = Fee.FIXED(amount=5_000)

    request = ClaimDepositRequest(txid=txid, vout=vout, max_fee=max_fee)

    response = await sdk.claim_deposit(request=request)
    logging.info(f"Deposit claimed successfully. Payment: {response.payment}")
except Exception as error:
    logging.error(error)
    raise
Go
txid := "<your_deposit_txid>"
vout := uint32(0)

request := breez_sdk_spark.ClaimDepositRequest{
	Txid: txid,
	Vout: vout,
}
response, err := sdk.ClaimDeposit(request)

if sdkErr := err.(*breez_sdk_spark.SdkError); sdkErr != nil {
	return nil, err
}

payment := response.Payment

Refunding a deposit

When a deposit cannot be successfully claimed, you can refund the funds to an external Bitcoin address. This operation creates a transaction that sends the deposit amount (minus transaction fees) to the specified destination address.

Rust
let txid = "your_deposit_txid".to_string();
let vout = 0;
let destination_address = "bc1qexample...".to_string(); // Your Bitcoin address

// Set the fee for the refund transaction
let fee = Fee::Fixed { amount: 500 };

let request = RefundDepositRequest {
    txid,
    vout,
    destination_address,
    fee,
};

let response = sdk.refund_deposit(request).await?;
info!("Refund transaction created:");
info!("Transaction ID: {}", response.tx_id);
info!("Transaction hex: {}", response.tx_hex);
Swift
let txid = "your_deposit_txid"
let vout: UInt32 = 0
let destinationAddress = "bc1qexample..." // Your Bitcoin address
// Set the fee for the refund transaction
let fee = Fee.fixed(amount: 500) // 500 sats

let request = RefundDepositRequest(
    txid: txid,
    vout: vout,
    destinationAddress: destinationAddress,
    fee: fee
)

let response = try await sdk.refundDeposit(request: request)
print("Refund transaction created:")
print("Transaction ID: \(response.txId)")
print("Transaction hex: \(response.txHex)")
Kotlin
try {
    val txid = "your_deposit_txid"
    val vout = 0u
    val destinationAddress = "bc1qexample..." // Your Bitcoin address

    // Set the fee for the refund transaction
    val fee = Fee.Fixed(500u)

    val request = RefundDepositRequest(
        txid = txid,
        vout = vout,
        destinationAddress = destinationAddress,
        fee = fee
    )

    val response = sdk.refundDeposit(request)
    // Log.v("Breez", "Refund transaction created:")
    // Log.v("Breez", "Transaction ID: ${response.txId}")
    // Log.v("Breez", "Transaction hex: ${response.txHex}")
} catch (e: Exception) {
    // handle error
}
C#
var txid = "your_deposit_txid";
var vout = 0U;
var destinationAddress = "bc1qexample...";  // Your Bitcoin address

// Set the fee for the refund transaction
var fee = new Fee.Fixed(amount: 500);

var request = new RefundDepositRequest(
    txid: txid,
    vout: vout,
    destinationAddress: destinationAddress,
    fee: fee
);

var response = await sdk.RefundDeposit(request: request);
Console.WriteLine("Refund transaction created:");
Console.WriteLine($"Transaction ID: {response.txId}");
Console.WriteLine($"Transaction hex: {response.txHex}");
Javascript
const txid = 'your_deposit_txid'
const vout = 0
const destinationAddress = 'bc1qexample...' // Your Bitcoin address

// Set the fee for the refund transaction
const fee: Fee = { type: 'fixed', amount: 500 }

const request: RefundDepositRequest = {
  txid,
  vout,
  destinationAddress,
  fee
}

const response = await sdk.refundDeposit(request)
console.log('Refund transaction created:')
console.log('Transaction ID:', response.txId)
console.log('Transaction hex:', response.txHex)
React Native
const txid = 'your_deposit_txid'
const vout = 0
const destinationAddress = 'bc1qexample...' // Your Bitcoin address

// Set the fee for the refund transaction
const fee = new Fee.Fixed({ amount: BigInt(500) })

const request: RefundDepositRequest = {
  txid,
  vout,
  destinationAddress,
  fee
}

const response = await sdk.refundDeposit(request)
console.log('Refund transaction created:')
console.log('Transaction ID:', response.txId)
console.log('Transaction hex:', response.txHex)
Flutter
String txid = "your_deposit_txid";
int vout = 0;
String destinationAddress = "bc1qexample..."; // Your Bitcoin address

// Set the fee for the refund transaction
Fee fee = Fee.fixed(amount: BigInt.from(500));

final request = RefundDepositRequest(
  txid: txid,
  vout: vout,
  destinationAddress: destinationAddress,
  fee: fee,
);

final response = await sdk.refundDeposit(request: request);
print("Refund transaction created:");
print("Transaction ID: ${response.txId}");
print("Transaction hex: ${response.txHex}");
Python
try:
    txid = "your_deposit_txid"
    vout = 0
    destination_address = "bc1qexample..."  # Your Bitcoin address

    # Set the fee for the refund transaction
    fee = Fee.FIXED(amount=500)

    request = RefundDepositRequest(
        txid=txid, vout=vout, destination_address=destination_address, fee=fee
    )

    response = await sdk.refund_deposit(request=request)
    logging.info("Refund transaction created:")
    logging.info(f"Transaction ID: {response.tx_id}")
    logging.info(f"Transaction hex: {response.tx_hex}")
except Exception as error:
    logging.error(error)
    raise
Go
txid := "<your_deposit_txid>"
vout := uint32(0)
destinationAddress := "bc1qexample..." // Your Bitcoin address

request := breez_sdk_spark.RefundDepositRequest{
	Txid:               txid,
	Vout:               vout,
	DestinationAddress: destinationAddress,
}
response, err := sdk.RefundDeposit(request)

if sdkErr := err.(*breez_sdk_spark.SdkError); sdkErr != nil {
	return err
}

log.Print("Refund transaction created:")
log.Printf("Transaction ID: %v", response.TxId)
log.Printf("Transaction hex: %v", response.TxHex)

Best Practices

  • Monitor events: Listen for UnclaimedDeposits events to be notified when deposits require manual intervention
  • Check claim errors: Examine the claim_error field in deposit information to understand why claims failed
  • Fee management: For fee-related failures, consider retrying with higher maximum fees during network congestion
  • Refund: Use refunding when claims consistently fail or when you need immediate access to funds and want to avoid the double-fee scenario (claim fee + cooperative exit fee)