Refunding Payments
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 Deposit Claim Failures
Deposit claims can fail for several reasons:
- Insufficient fee configuration: The maximum configured fees may be 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
When deposit claims fail, the SDK emits a ClaimDepositsFailed
event containing information about the unclaimed deposits, including the specific error that caused each failure.
Managing Failed Deposits
The SDK provides three methods to handle failed deposit claims:
- List unclaimed deposits - Retrieve all deposits that failed to be claimed
- Retry claiming - Attempt to claim a deposit again with different parameters
- Refund deposit - Send the deposit funds to an external Bitcoin address
Listing Unclaimed Deposits
Use the list_unclaimed_deposits
method to retrieve all deposits that have failed to be claimed. This method returns detailed information about each failed deposit, including the specific error that caused the failure.
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!("Claim failed: Fee exceeded. Max: {}, Actual: {}", max_fee, actual_fee);
}
DepositClaimError::MissingUtxo { .. } => {
info!("Claim failed: UTXO not found");
}
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("Claim failed: Fee exceeded. Max: \(maxFee), Actual: \(actualFee)")
case .missingUtxo(let tx, let vout):
print("Claim failed: UTXO not found")
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", "Claim failed: Fee exceeded. Max: ${claimError.maxFee}, Actual: ${claimError.actualFee}")
}
is DepositClaimError.MissingUtxo -> {
// Log.v("Breez", "Claim failed: UTXO not found")
}
is DepositClaimError.Generic -> {
// Log.v("Breez", "Claim failed: ${claimError.message}")
}
}
}
}
} catch (e: Exception) {
// handle error
}
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) {
switch (deposit.claimError.type) {
case 'depositClaimFeeExceeded':
console.log(`Claim failed: Fee exceeded. Max: ${deposit.claimError.maxFee}, Actual: ${deposit.claimError.actualFee}`)
break
case 'missingUtxo':
console.log('Claim failed: UTXO not found')
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) {
if (deposit.claimError instanceof DepositClaimError.DepositClaimFeeExceeded) {
console.log(
`Claim failed: Fee exceeded. ` +
`Max: ${deposit.claimError.inner.maxFee}, ` +
`Actual: ${deposit.claimError.inner.actualFee}`
)
} else if (deposit.claimError instanceof DepositClaimError.MissingUtxo) {
console.log('Claim failed: UTXO not found')
} 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(
"Claim failed: Fee exceeded. Max: ${claimError.maxFee}, Actual: ${claimError.actualFee}");
} else if (claimError is DepositClaimError_MissingUtxo) {
print("Claim failed: UTXO not found");
} 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("Claim failed: Fee exceeded. Max: %v, Actual: %v", claimErr.MaxFee, claimErr.ActualFee)
case breez_sdk_spark.DepositClaimErrorMissingUtxo:
log.Print("Claim failed: UTXO not found")
case breez_sdk_spark.DepositClaimErrorGeneric:
log.Printf("Claim failed: %v", claimErr.Message)
case nil:
log.Printf("No claim error")
}
}
}
Retrying Deposit Claims
If a deposit claim failed 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
}
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 Deposits
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
}
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
ClaimDepositsFailed
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)