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

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:

  1. List unclaimed deposits - Retrieve all deposits that failed to be claimed
  2. Retry claiming - Attempt to claim a deposit again with different parameters
  3. 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.

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!("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);
            }
        }
    }
}
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("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)")
        }
    }
}
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", "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
}
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) {
    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
    }
  }
}
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) {
    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}`)
    }
  }
}
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(
        "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}");
  }
}
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("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.

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
}
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 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.

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
}
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 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)