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

Spark HTLC payments

Hash Time-Locked Contract (HTLC) payments are conditional payments that enable atomic cross-chain swaps. The SDK supports Spark HTLCs through a simple API.

In an HTLC payment, the sender locks funds using a cryptographic hash of a secret preimage and sets an expiration time. The receiver can claim the payment by revealing the preimage before expiration. If the receiver fails to claim the payment in time, the funds are automatically returned to the sender.

Sending HTLC Payments

HTLC payments use the standard payment API described in Sending payments. To create an HTLC payment, prepare the payment normally, then provide the Spark HTLC options when sending. These options include the payment hash (SHA-256 hash of the preimage) and the expiry duration.

Rust
let payment_request = "<spark address>".to_string();
// Set the amount you wish the pay the receiver
let amount_sats = Some(50_000);
let prepare_request = PrepareSendPaymentRequest {
    payment_request,
    amount: amount_sats,
    token_identifier: None,
};
let prepare_response = sdk.prepare_send_payment(prepare_request).await?;

// If the fees are acceptable, continue to create the HTLC Payment
if let SendPaymentMethod::SparkAddress { fee, .. } = prepare_response.payment_method {
    info!("Fees: {} sats", fee);
}

let preimage = "<32-byte unique preimage hex>";
let preimage_bytes = hex::decode(preimage)?;
let payment_hash_bytes = sha256::digest(preimage_bytes);
let payment_hash = hex::encode(payment_hash_bytes);

// Set the HTLC options
let options = SendPaymentOptions::SparkAddress {
    htlc_options: Some(SparkHtlcOptions {
        payment_hash,
        expiry_duration_secs: 1000,
    }),
};

let request = SendPaymentRequest {
    prepare_response,
    options: Some(options),
    idempotency_key: None,
};
let send_response = sdk.send_payment(request).await?;
let payment = send_response.payment;
Swift
let paymentRequest = "<spark address>"
// Set the amount you wish the pay the receiver (requires 'import BigNumber')
let amountSats = BInt(50_000)
let prepareRequest = PrepareSendPaymentRequest(
    paymentRequest: paymentRequest,
    amount: amountSats
)
let prepareResponse = try await sdk.prepareSendPayment(request: prepareRequest)

// If the fees are acceptable, continue to create the HTLC Payment
if case let .sparkAddress(_, fee, _) = prepareResponse.paymentMethod {
    print("Fees: \(fee) sats")
}

let preimage = "<32-byte unique preimage hex>"
let preimageData = Data(hexString: preimage)!
let paymentHashDigest = SHA256.hash(data: preimageData)
let paymentHash = Data(paymentHashDigest).hexEncodedString()

// Set the HTLC options
let htlcOptions = SparkHtlcOptions(
    paymentHash: paymentHash,
    expiryDurationSecs: 1000
)
let options = SendPaymentOptions.sparkAddress(htlcOptions: htlcOptions)

let request = SendPaymentRequest(
    prepareResponse: prepareResponse,
    options: options
)
let sendResponse = try await sdk.sendPayment(request: request)
let payment = sendResponse.payment
Kotlin
val paymentRequest = "<spark address>"
// Set the amount you wish the pay the receiver
// Kotlin MPP (BigInteger from com.ionspin.kotlin.bignum.integer, which is included in package)
val amountSats = BigInteger.fromLong(50_000L)
// Android (BigInteger from java.math)
// val amountSats = BigInteger.valueOf(50_000L)
try {
    val prepareRequest = PrepareSendPaymentRequest(paymentRequest, amountSats)
    val prepareResponse = sdk.prepareSendPayment(prepareRequest)

    // If the fees are acceptable, continue to create the HTLC Payment
    val paymentMethod = prepareResponse.paymentMethod
    if (paymentMethod is SendPaymentMethod.SparkAddress) {
        val fee = paymentMethod.fee
        // Log.v("Breez", "Fees: ${fee} sats")
    }

    val preimage = "<32-byte unique preimage hex>"
    val preimageBytes = preimage.hexToByteArray()
    val digest = SHA256()
    digest.update(preimageBytes)
    val paymentHashBytes = digest.digest()
    val paymentHash = paymentHashBytes.toHexString()

    // Set the HTLC options
    val htlcOptions = SparkHtlcOptions(
        paymentHash = paymentHash,
        expiryDurationSecs = 1000u
    )
    val options = SendPaymentOptions.SparkAddress(htlcOptions = htlcOptions)

    val request = SendPaymentRequest(
        prepareResponse = prepareResponse,
        options = options
    )
    val sendResponse = sdk.sendPayment(request)
    val payment = sendResponse.payment
} catch (e: Exception) {
    // handle error
    throw e
}
C#
var paymentRequest = "<spark address>";
// Set the amount you wish the pay the receiver
var amountSats = new BigInteger(50000);
var prepareRequest = new PrepareSendPaymentRequest(
    paymentRequest: paymentRequest,
    amount: amountSats
);
var prepareResponse = await sdk.PrepareSendPayment(request: prepareRequest);

// If the fees are acceptable, continue to create the HTLC Payment
if (prepareResponse.paymentMethod is SendPaymentMethod.SparkAddress sparkMethod)
{
    var fee = sparkMethod.fee;
    Console.WriteLine($"Fees: {fee} sats");
}

var preimage = "<32-byte unique preimage hex>";
var preimageBytes = Convert.FromHexString(preimage);
var paymentHashBytes = System.Security.Cryptography.SHA256.HashData(preimageBytes);
var paymentHash = Convert.ToHexString(paymentHashBytes).ToLower();

// Set the HTLC options
var options = new SendPaymentOptions.SparkAddress(
    htlcOptions: new SparkHtlcOptions(
        paymentHash: paymentHash,
        expiryDurationSecs: 1000
    )
);

var request = new SendPaymentRequest(
    prepareResponse: prepareResponse,
    options: options
);
var sendResponse = await sdk.SendPayment(request: request);
var payment = sendResponse.payment;
Javascript
const paymentRequest = '<spark address>'
// Set the amount you wish the pay the receiver
const amountSats = BigInt(50000)
const prepareRequest = {
  paymentRequest,
  amount: amountSats
}
const prepareResponse = await sdk.prepareSendPayment(prepareRequest)

// If the fees are acceptable, continue to create the HTLC Payment
if (prepareResponse.paymentMethod.type === 'sparkAddress') {
  const fee = prepareResponse.paymentMethod.fee
  console.debug(`Fees: ${fee} sats`)
}

const preimage = '<32-byte unique preimage hex>'
const preimageBuffer = Buffer.from(preimage, 'hex')
const paymentHash = createHash('sha256').update(preimageBuffer).digest('hex')

const sendResponse = await sdk.sendPayment({
  prepareResponse,
  options: {
    type: 'sparkAddress',
    htlcOptions: {
      paymentHash,
      expiryDurationSecs: 1000
    }
  }
})
const payment = sendResponse.payment
React Native
const paymentRequest = '<spark address>'
// Set the amount you wish the pay the receiver
const amountSats = BigInt(50000)
const prepareRequest = {
  paymentRequest,
  amount: amountSats,
  tokenIdentifier: undefined
}
const prepareResponse = await sdk.prepareSendPayment(prepareRequest)

// If the fees are acceptable, continue to create the HTLC Payment
if (prepareResponse.paymentMethod?.tag === SendPaymentMethod_Tags.SparkAddress) {
  const fee = prepareResponse.paymentMethod.inner.fee
  console.debug(`Fees: ${fee} sats`)
}

const preimage = '<32-byte unique preimage hex>'
const preimageBuffer = Buffer.from(preimage, 'hex')
const paymentHash = createHash('sha256').update(preimageBuffer).digest('hex')

// Set the HTLC options
const options = new SendPaymentOptions.SparkAddress({
  htlcOptions: {
    paymentHash,
    expiryDurationSecs: BigInt(1000)
  }
})

const request = {
  prepareResponse,
  options,
  idempotencyKey: undefined
}
const sendResponse = await sdk.sendPayment(request)
const payment = sendResponse.payment
Flutter
String paymentRequest = "<spark address>";
// Set the amount you wish the pay the receiver
BigInt amountSats = BigInt.from(50000);
final prepareRequest = PrepareSendPaymentRequest(
    paymentRequest: paymentRequest, amount: amountSats);
final prepareResponse = await sdk.prepareSendPayment(request: prepareRequest);

// If the fees are acceptable, continue to create the HTLC Payment
final paymentMethod = prepareResponse.paymentMethod;
if (paymentMethod is SendPaymentMethod_SparkAddress) {
  final fee = paymentMethod.fee;
  print("Fees: $fee sats");
}

String preimage = "<32-byte unique preimage hex>";
List<int> preimageBytes = hex.decode(preimage);
Digest paymentHashDigest = sha256.convert(preimageBytes);
String paymentHash = hex.encode(paymentHashDigest.bytes);

// Set the HTLC options
final htlcOptions = SparkHtlcOptions(
    paymentHash: paymentHash, expiryDurationSecs: BigInt.from(1000));
final options = SendPaymentOptions.sparkAddress(htlcOptions: htlcOptions);

final request =
    SendPaymentRequest(prepareResponse: prepareResponse, options: options);
final sendResponse = await sdk.sendPayment(request: request);
final payment = sendResponse.payment;
Python
payment_request = "<spark address>"
# Set the amount you wish the pay the receiver
amount_sats = 50_000
prepare_request = PrepareSendPaymentRequest(
    payment_request=payment_request, amount=amount_sats
)
prepare_response = await sdk.prepare_send_payment(request=prepare_request)

# If the fees are acceptable, continue to create the HTLC Payment
if hasattr(prepare_response.payment_method, "fee"):
    fee = prepare_response.payment_method.fee
    logging.debug(f"Fees: {fee} sats")

preimage = "<32-byte unique preimage hex>"
preimage_bytes = bytes.fromhex(preimage)
payment_hash_bytes = hashlib.sha256(preimage_bytes).digest()
payment_hash = payment_hash_bytes.hex()

# Set the HTLC options
options = SendPaymentOptions.SPARK_ADDRESS(
    htlc_options=SparkHtlcOptions(
        payment_hash=payment_hash, expiry_duration_secs=1000
    )
)

request = SendPaymentRequest(
    prepare_response=prepare_response, options=options
)
send_response = await sdk.send_payment(request=request)
payment = send_response.payment
Go
paymentRequest := "<spark address>"
// Set the amount you wish the pay the receiver
amountSats := new(big.Int).SetInt64(50_000)
prepareRequest := breez_sdk_spark.PrepareSendPaymentRequest{
	PaymentRequest: paymentRequest,
	Amount:         &amountSats,
}
prepareResponse, err := sdk.PrepareSendPayment(prepareRequest)

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

// If the fees are acceptable, continue to create the HTLC Payment
switch paymentMethod := prepareResponse.PaymentMethod.(type) {
case breez_sdk_spark.SendPaymentMethodSparkAddress:
	fee := paymentMethod.Fee
	log.Printf("Fees: %v sats", fee)
}

preimage := "<32-byte unique preimage hex>"
preimageBytes, err := hex.DecodeString(preimage)
if err != nil {
	return nil, err
}
paymentHashBytes := sha256.Sum256(preimageBytes)
paymentHash := hex.EncodeToString(paymentHashBytes[:])

// Set the HTLC options
htlcOptions := breez_sdk_spark.SparkHtlcOptions{
	PaymentHash:        paymentHash,
	ExpiryDurationSecs: 1000,
}
var options breez_sdk_spark.SendPaymentOptions = breez_sdk_spark.SendPaymentOptionsSparkAddress{
	HtlcOptions: &htlcOptions,
}

request := breez_sdk_spark.SendPaymentRequest{
	PrepareResponse: prepareResponse,
	Options:         &options,
}
sendResponse, err := sdk.SendPayment(request)

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

payment := sendResponse.Payment

Developer note

Preimages are required to be unique and are not managed by the SDK. It is your responsibility as a developer to manage them, including how to generate them, store them, and eventually share them with the receiver.

Listing claimable HTLC payments API docs

Once detected, claimable HTLC payments are immediately listed as pending in the list of payments. Additionally, a PaymentPending event is emitted to notify your application. See Listening to events for more details.

To list only claimable HTLC payments, you can filter by HTLC status.

Rust
let request = ListPaymentsRequest {
    type_filter: Some(vec![PaymentType::Receive]),
    status_filter: Some(vec![PaymentStatus::Pending]),
    spark_htlc_status_filter: Some(vec![SparkHtlcStatus::WaitingForPreimage]),
    ..Default::default()
};

let response = sdk.list_payments(request).await?;
let payments = response.payments;
Swift
let request = ListPaymentsRequest(
    typeFilter: [PaymentType.receive],
    statusFilter: [PaymentStatus.pending],
    sparkHtlcStatusFilter: [SparkHtlcStatus.waitingForPreimage]
)

let response = try await sdk.listPayments(request: request)
let payments = response.payments
Kotlin
try {
    val request = ListPaymentsRequest(
        typeFilter = listOf(PaymentType.RECEIVE),
        statusFilter = listOf(PaymentStatus.PENDING),
        sparkHtlcStatusFilter = listOf(SparkHtlcStatus.WAITING_FOR_PREIMAGE)
    )

    val response = sdk.listPayments(request)
    val payments = response.payments
} catch (e: Exception) {
    // handle error
    throw e
}
C#
var request = new ListPaymentsRequest(
    typeFilter: new List<PaymentType> { PaymentType.Receive },
    statusFilter: new List<PaymentStatus> { PaymentStatus.Pending },
    sparkHtlcStatusFilter: new List<SparkHtlcStatus> {
        SparkHtlcStatus.WaitingForPreimage
    }
);

var response = await sdk.ListPayments(request: request);
var payments = response.payments;
Javascript
const response = await sdk.listPayments({
  typeFilter: ['receive'],
  statusFilter: ['pending'],
  sparkHtlcStatusFilter: ['waitingForPreimage']
})
const payments = response.payments
React Native
const request = {
  typeFilter: [PaymentType.Receive],
  statusFilter: [PaymentStatus.Pending],
  sparkHtlcStatusFilter: [SparkHtlcStatus.WaitingForPreimage],
  assetFilter: undefined,
  fromTimestamp: undefined,
  toTimestamp: undefined,
  offset: undefined,
  limit: undefined,
  sortAscending: undefined
}

const response = await sdk.listPayments(request)
const payments = response.payments
Flutter
final request = ListPaymentsRequest(
  typeFilter: [PaymentType.receive],
  statusFilter: [PaymentStatus.pending],
  sparkHtlcStatusFilter: [SparkHtlcStatus.waitingForPreimage],
);

final response = await sdk.listPayments(request: request);
final payments = response.payments;
Python
request = ListPaymentsRequest(
    type_filter=[PaymentType.RECEIVE],
    status_filter=[PaymentStatus.PENDING],
    spark_htlc_status_filter=[SparkHtlcStatus.WAITING_FOR_PREIMAGE],
)

response = await sdk.list_payments(request=request)
payments = response.payments
Go
typeFilter := []breez_sdk_spark.PaymentType{
	breez_sdk_spark.PaymentTypeReceive,
}
statusFilter := []breez_sdk_spark.PaymentStatus{
	breez_sdk_spark.PaymentStatusPending,
}
sparkHtlcStatusFilter := []breez_sdk_spark.SparkHtlcStatus{
	breez_sdk_spark.SparkHtlcStatusWaitingForPreimage,
}

request := breez_sdk_spark.ListPaymentsRequest{
	TypeFilter:            &typeFilter,
	StatusFilter:          &statusFilter,
	SparkHtlcStatusFilter: &sparkHtlcStatusFilter,
}

response, err := sdk.ListPayments(request)

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

payments := response.Payments

Claiming HTLC Payments API docs

To claim an HTLC payment, provide the preimage that matches the payment hash.

Rust
let preimage = "<preimage hex>".to_string();
let response = sdk
    .claim_htlc_payment(ClaimHtlcPaymentRequest { preimage })
    .await?;
let payment = response.payment;
Swift
let preimage = "<preimage hex>"
let response = try await sdk.claimHtlcPayment(
    request: ClaimHtlcPaymentRequest(preimage: preimage)
)
let payment = response.payment
Kotlin
try {
    val preimage = "<preimage hex>"
    val request = ClaimHtlcPaymentRequest(preimage = preimage)
    val response = sdk.claimHtlcPayment(request)
    val payment = response.payment
} catch (e: Exception) {
    // handle error
    throw e
}
C#
var preimage = "<preimage hex>";
var response = await sdk.ClaimHtlcPayment(
    request: new ClaimHtlcPaymentRequest(preimage: preimage)
);
var payment = response.payment;
Javascript
const preimage = '<preimage hex>'
const response = await sdk.claimHtlcPayment({
  preimage
})
const payment = response.payment
React Native
const preimage = '<preimage hex>'
const response = await sdk.claimHtlcPayment(
  { preimage }
)
const payment = response.payment
Flutter
String preimage = "<preimage hex>";
final response = await sdk.claimHtlcPayment(
    request: ClaimHtlcPaymentRequest(preimage: preimage));
final payment = response.payment;
Python
preimage = "<preimage hex>"
response = await sdk.claim_htlc_payment(
    request=ClaimHtlcPaymentRequest(preimage=preimage)
)
payment = response.payment
Go
preimage := "<preimage hex>"
request := breez_sdk_spark.ClaimHtlcPaymentRequest{
	Preimage: preimage,
}
response, err := sdk.ClaimHtlcPayment(request)

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

payment := response.Payment