Add descriptionHash parameter to createinvoice (#50)

A new parameter `descriptionHash` has been added to `createinvoice`, which takes a 32-bytes hex string.

Either `description` or `descriptionHash` must be provided.

The `description` field is now limited to 128 characters.
This commit is contained in:
Pierre-Marie Padiou 2024-05-15 10:55:16 +02:00 committed by GitHub
parent 18035566f8
commit dec1c59c80
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
2 changed files with 33 additions and 8 deletions

View File

@ -113,8 +113,16 @@ class Api(private val nodeParams: NodeParams, private val peer: Peer, private va
post("createinvoice") {
val formParameters = call.receiveParameters()
val amount = formParameters.getOptionalLong("amountSat")?.sat
val description = formParameters.getString("description")
val invoice = peer.createInvoice(randomBytes32(), amount?.toMilliSatoshi(), Either.Left(description))
val maxDescriptionSize = 128
val description = formParameters["description"]
?.also { if (it.length > maxDescriptionSize) badRequest("Request parameter description is too long (max $maxDescriptionSize characters)") }
val descriptionHash = formParameters.getOptionalByteVector32("descriptionHash")
val eitherDesc = when {
description != null && descriptionHash == null -> Either.Left(description)
description == null && descriptionHash != null -> Either.Right(descriptionHash)
else -> badRequest("Must provide either a description or descriptionHash")
}
val invoice = peer.createInvoice(randomBytes32(), amount?.toMilliSatoshi(), eitherDesc)
formParameters["externalId"]?.takeUnless { it.isBlank() }?.let { externalId ->
paymentDb.metadataQueries.insertExternalId(WalletPaymentId.IncomingPaymentId(invoice.paymentHash), externalId)
}
@ -228,13 +236,17 @@ class Api(private val nodeParams: NodeParams, private val peer: Peer, private va
private fun invalidType(argName: String, typeName: String): Nothing = throw ParameterConversionException(argName, typeName)
private fun badRequest(message: String): Nothing = throw BadRequestException(message)
private fun Parameters.getString(argName: String): String = (this[argName] ?: missing(argName))
private fun Parameters.getByteVector32(argName: String): ByteVector32 = getString(argName).let { hex -> kotlin.runCatching { ByteVector32.fromValidHex(hex) }.getOrNull() ?: invalidType(argName, "hex32") }
private fun Parameters.getOptionalByteVector32(argName: String): ByteVector32? = this[argName]?.let { hex -> kotlin.runCatching { ByteVector32.fromValidHex(hex) }.getOrNull() ?: invalidType(argName, "hex32") }
private fun Parameters.getUUID(argName: String): UUID = getString(argName).let { uuid -> kotlin.runCatching { UUID.fromString(uuid) }.getOrNull() ?: invalidType(argName, "uuid") }
private fun Parameters.getAddressAndConvertToScript(argName: String): ByteVector = Script.write(Bitcoin.addressToPublicKeyScript(nodeParams.chainHash, getString(argName)).right ?: error("invalid address")).toByteVector()
private fun Parameters.getAddressAndConvertToScript(argName: String): ByteVector = Script.write(Bitcoin.addressToPublicKeyScript(nodeParams.chainHash, getString(argName)).right ?: badRequest("Invalid address")).toByteVector()
private fun Parameters.getInvoice(argName: String): Bolt11Invoice = getString(argName).let { invoice -> Bolt11Invoice.read(invoice).getOrElse { invalidType(argName, "bolt11invoice") } }

View File

@ -5,6 +5,9 @@ import com.github.ajalt.clikt.core.context
import com.github.ajalt.clikt.core.requireObject
import com.github.ajalt.clikt.core.subcommands
import com.github.ajalt.clikt.output.MordantHelpFormatter
import com.github.ajalt.clikt.parameters.groups.mutuallyExclusiveOptions
import com.github.ajalt.clikt.parameters.groups.required
import com.github.ajalt.clikt.parameters.groups.single
import com.github.ajalt.clikt.parameters.options.*
import com.github.ajalt.clikt.parameters.types.int
import com.github.ajalt.clikt.parameters.types.long
@ -12,6 +15,7 @@ import com.github.ajalt.clikt.sources.MapValueSource
import fr.acinq.bitcoin.Base58Check
import fr.acinq.bitcoin.Bech32
import fr.acinq.bitcoin.ByteVector32
import fr.acinq.bitcoin.utils.Either
import fr.acinq.lightning.BuildVersions
import fr.acinq.lightning.bin.conf.readConfFile
import fr.acinq.lightning.bin.datadir
@ -124,7 +128,7 @@ class GetOutgoingPayment : PhoenixCliCommand(name = "getoutgoingpayment", help =
}
class GetIncomingPayment : PhoenixCliCommand(name = "getincomingpayment", help = "Get incoming payment") {
private val paymentHash by option("--paymentHash", "--h").convert { ByteVector32.fromValidHex(it) }.required()
private val paymentHash by option("--paymentHash", "--h").convert { it.toByteVector32() }.required()
override suspend fun httpRequest() = commonOptions.httpClient.use {
it.get(url = commonOptions.baseUrl / "payments/incoming/$paymentHash")
}
@ -143,7 +147,11 @@ class ListIncomingPayments : PhoenixCliCommand(name = "listincomingpayments", he
class CreateInvoice : PhoenixCliCommand(name = "createinvoice", help = "Create a Lightning invoice", printHelpOnEmptyArgs = true) {
private val amountSat by option("--amountSat").long()
private val description by option("--description", "--desc").required()
private val description by mutuallyExclusiveOptions(
option("--description", "--desc").convert { Either.Left(it) },
option("--descriptionHash", "--desc-hash").convert { Either.Right(it.toByteVector32()) }
).single().required()
private val externalId by option("--externalId")
override suspend fun httpRequest() = commonOptions.httpClient.use {
it.submitForm(
@ -151,7 +159,10 @@ class CreateInvoice : PhoenixCliCommand(name = "createinvoice", help = "Create a
formParameters = parameters {
amountSat?.let { append("amountSat", it.toString()) }
externalId?.let { append("externalId", it) }
append("description", description)
when(val d = description) {
is Either.Left -> append("description", d.value)
is Either.Right -> append("descriptionHash", d.value.toHex())
}
}
)
}
@ -188,7 +199,7 @@ class SendToAddress : PhoenixCliCommand(name = "sendtoaddress", help = "Send to
}
class CloseChannel : PhoenixCliCommand(name = "closechannel", help = "Close channel", printHelpOnEmptyArgs = true) {
private val channelId by option("--channelId").convert { ByteVector32.fromValidHex(it) }.required()
private val channelId by option("--channelId").convert { it.toByteVector32() }.required()
private val address by option("--address").required().check { runCatching { Base58Check.decode(it) }.isSuccess || runCatching { Bech32.decodeWitnessAddress(it) }.isSuccess }
private val feerateSatByte by option("--feerateSatByte").int().required()
override suspend fun httpRequest() = commonOptions.httpClient.use {
@ -204,3 +215,5 @@ class CloseChannel : PhoenixCliCommand(name = "closechannel", help = "Close chan
}
operator fun Url.div(path: String) = Url(URLBuilder(this).appendPathSegments(path))
fun String.toByteVector32(): ByteVector32 = kotlin.runCatching { ByteVector32.fromValidHex(this) }.recover { error("'$this' is not a valid 32-bytes hex string") }.getOrThrow()