From ef3f6cca4571006c486b54349641ba3d8e3922d4 Mon Sep 17 00:00:00 2001 From: Matthew Rheaume Date: Thu, 15 Jun 2023 16:43:27 -0700 Subject: [PATCH] [kt-sdk] Added create_lnurl_invoice mutation support. --- .../lightspark/sdk/LightsparkFuturesClient.kt | 17 ++++++++ .../sdk/LightsparkCoroutinesClient.kt | 39 +++++++++++++++++++ .../lightspark/sdk/LightsparkSyncClient.kt | 16 ++++++++ .../sdk/graphql/CreateLnurlInvoice.kt | 21 ++++++++++ .../lightspark/sdk/ClientIntegrationTests.kt | 9 +++++ 5 files changed, 102 insertions(+) create mode 100644 lightspark-sdk/src/commonMain/kotlin/com/lightspark/sdk/graphql/CreateLnurlInvoice.kt diff --git a/lightspark-sdk/src/commonJvmAndroidMain/kotlin/com/lightspark/sdk/LightsparkFuturesClient.kt b/lightspark-sdk/src/commonJvmAndroidMain/kotlin/com/lightspark/sdk/LightsparkFuturesClient.kt index 3e1d9b5f..61c02a8f 100644 --- a/lightspark-sdk/src/commonJvmAndroidMain/kotlin/com/lightspark/sdk/LightsparkFuturesClient.kt +++ b/lightspark-sdk/src/commonJvmAndroidMain/kotlin/com/lightspark/sdk/LightsparkFuturesClient.kt @@ -127,6 +127,23 @@ class LightsparkFuturesClient(config: ClientConfig) { ): CompletableFuture = 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 = + coroutineScope.future { coroutinesClient.createLnurlInvoice(nodeId, amountMsats, metadata) } + /** * Pay a lightning invoice for the given node. * diff --git a/lightspark-sdk/src/commonMain/kotlin/com/lightspark/sdk/LightsparkCoroutinesClient.kt b/lightspark-sdk/src/commonMain/kotlin/com/lightspark/sdk/LightsparkCoroutinesClient.kt index 636c8acd..2d59ef75 100644 --- a/lightspark-sdk/src/commonMain/kotlin/com/lightspark/sdk/LightsparkCoroutinesClient.kt +++ b/lightspark-sdk/src/commonMain/kotlin/com/lightspark/sdk/LightsparkCoroutinesClient.kt @@ -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.* @@ -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") + 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. * diff --git a/lightspark-sdk/src/commonMain/kotlin/com/lightspark/sdk/LightsparkSyncClient.kt b/lightspark-sdk/src/commonMain/kotlin/com/lightspark/sdk/LightsparkSyncClient.kt index c0a34ed2..0adbe621 100644 --- a/lightspark-sdk/src/commonMain/kotlin/com/lightspark/sdk/LightsparkSyncClient.kt +++ b/lightspark-sdk/src/commonMain/kotlin/com/lightspark/sdk/LightsparkSyncClient.kt @@ -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. * diff --git a/lightspark-sdk/src/commonMain/kotlin/com/lightspark/sdk/graphql/CreateLnurlInvoice.kt b/lightspark-sdk/src/commonMain/kotlin/com/lightspark/sdk/graphql/CreateLnurlInvoice.kt new file mode 100644 index 00000000..c7ff367b --- /dev/null +++ b/lightspark-sdk/src/commonMain/kotlin/com/lightspark/sdk/graphql/CreateLnurlInvoice.kt @@ -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} +""" diff --git a/lightspark-sdk/src/commonTest/kotlin/com/lightspark/sdk/ClientIntegrationTests.kt b/lightspark-sdk/src/commonTest/kotlin/com/lightspark/sdk/ClientIntegrationTests.kt index 85e62c8b..ad7d53e3 100644 --- a/lightspark-sdk/src/commonTest/kotlin/com/lightspark/sdk/ClientIntegrationTests.kt +++ b/lightspark-sdk/src/commonTest/kotlin/com/lightspark/sdk/ClientIntegrationTests.kt @@ -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()