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:
- Listing unclaimed deposits - Retrieve all deposits that have not yet been claimed
- Claiming a deposit - Claim a deposit using specific claiming parameters
- 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.
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);
}
}
}
}
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)")
}
}
}
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
}
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}");
}
}
}
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
}
}
}
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}`)
}
}
}
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}");
}
}
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
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.
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);
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)")
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
}
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}");
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)
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)
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}");
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
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.
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);
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)")
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
}
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}");
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)
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)
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}");
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
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
UnclaimedDepositsevents to be notified when deposits require manual intervention - Check claim errors: Examine the
claim_errorfield 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)