Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -127,6 +127,23 @@ class LightsparkFuturesClient(config: ClientConfig) {
): CompletableFuture<InvoiceData> =
coroutineScope.future { coroutinesClient.createInvoice(nodeId, amountMsats, memo, type) }

/**
* Creates a Lightning invoice for the given node. This should only be used for generating invoices for LNURLs, with
* [LightsparkCoroutinesClient.createInvoice] preferred in the general case.
*
* @param nodeId The ID of the node for which to create the invoice.
* @param amountMsats The amount of the invoice in milli-satoshis.
* @param metadata The LNURL metadata payload field from the initial payreq response. This will be hashed and
* present in the h-tag (SHA256 purpose of payment) of the resulting Bolt 11 invoice.
*/
@JvmOverloads
fun createLnurlInvoice(
nodeId: String,
amountMsats: Long,
metadata: String
): CompletableFuture<InvoiceData> =
coroutineScope.future { coroutinesClient.createLnurlInvoice(nodeId, amountMsats, metadata) }

/**
* Pay a lightning invoice for the given node.
*
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,7 @@ import com.lightspark.sdk.graphql.*
import com.lightspark.sdk.model.*
import com.lightspark.sdk.util.serializerFormat
import saschpe.kase64.base64DecodedBytes
import java.security.MessageDigest
import kotlinx.coroutines.flow.Flow
import kotlinx.serialization.json.*

Expand Down Expand Up @@ -205,6 +206,44 @@ class LightsparkCoroutinesClient private constructor(
)
}

/**
* Creates a Lightning invoice for the given node. This should only be used for generating invoices for LNURLs, with
* [LightsparkCoroutinesClient.createInvoice] preferred in the general case.
*
* @param nodeId The ID of the node for which to create the invoice.
* @param amountMsats The amount of the invoice in milli-satoshis.
* @param metadata The LNURL metadata payload field from the initial payreq response. This will be hashed and
* present in the h-tag (SHA256 purpose of payment) of the resulting Bolt 11 invoice.
*/
suspend fun createLnurlInvoice(
nodeId: String,
amountMsats: Long,
metadata: String,
): InvoiceData {
requireValidAuth()

val md = MessageDigest.getInstance("SHA-256")
Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I can throw this in a separate function under utils if you'd like as well @jklein24 .

val digest = md.digest(metadata.toByteArray())
val metadataHash = digest.fold(StringBuilder()) { sb, it -> sb.append("%02x".format(it)) }.toString()

return executeQuery(
Query(
CreateLnurlInvoiceMutation,
{
add("nodeId", nodeId)
add("amountMsats", amountMsats)
add("metadataHash", metadataHash)
},
) {
val invoiceJson =
requireNotNull(
it["create_lnurl_invoice"]?.jsonObject?.get("invoice")?.jsonObject?.get("data"),
) { "No invoice found in response" }
serializerFormat.decodeFromJsonElement(invoiceJson)
},
)
}

/**
* Pay a lightning invoice for the given node.
*
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -116,6 +116,22 @@ class LightsparkSyncClient constructor(config: ClientConfig) {
type: InvoiceType = InvoiceType.STANDARD,
): InvoiceData = runBlocking { asyncClient.createInvoice(nodeId, amountMsats, memo, type) }

/**
* Creates a Lightning invoice for the given node. This should only be used for generating invoices for LNURLs, with
* [LightsparkCoroutinesClient.createInvoice] preferred in the general case.
*
* @param nodeId The ID of the node for which to create the invoice.
* @param amountMsats The amount of the invoice in milli-satoshis.
* @param metadata The LNURL metadata payload field from the initial payreq response. This will be hashed and
* present in the h-tag (SHA256 purpose of payment) of the resulting Bolt 11 invoice.
*/
@JvmOverloads
fun createLnurlInvoice(
nodeId: String,
amountMsats: Long,
metadata: String
): InvoiceData = runBlocking { asyncClient.createLnurlInvoice(nodeId, amountMsats, metadata) }

/**
* Pay a lightning invoice for the given node.
*
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,21 @@
package com.lightspark.sdk.graphql

import com.lightspark.sdk.model.InvoiceData

const val CreateLnurlInvoiceMutation = """
mutation CreateLnurlInvoice(
${'$'}nodeId: ID!
${'$'}amountMsats: Long!
${'$'}metadataHash: String!
) {
create_lnurl_invoice(input: { node_id: ${'$'}nodeId, amount_msats: ${'$'}amountMsats, metadata_hash: ${'$'}metadataHash }) {
invoice {
data {
...InvoiceDataFragment
}
}
}
}

${InvoiceData.FRAGMENT}
"""
Original file line number Diff line number Diff line change
Expand Up @@ -175,6 +175,15 @@ class ClientIntegrationTests {
println("decoded invoice: $decoded")
}

@Test
fun `create an LNURL invoice`() = runTest {
val node = getFirstNode()
val metadata = "[[\\\"text/plain\\\",\\\"Pay to domain.org user ktfan98\\\"],[\\\"text/identifier\\\",\\\"ktfan98@domain.org\\\"]]"
val paymentRequest = client.createLnurlInvoice(node.id, 1000, metadata)

println("encoded invoice: ${paymentRequest.encodedPaymentRequest}")
}

@Test
fun `send a payment for an invoice`() = runTest {
val node = getFirstNode()
Expand Down