feat: complete NIP-47 Nostr Wallet Connect implementation in Quartz
Implements the full NIP-47 specification with all request methods, response types, notification events, and JSON serialization. New request methods: - pay_invoice (updated with amount, metadata params) - pay_keysend (amount, pubkey, preimage, TLV records) - make_invoice, lookup_invoice, list_transactions - get_balance, get_info - make_hold_invoice, cancel_hold_invoice, settle_hold_invoice New response types: - Success responses for all methods above - NwcErrorResponse (generic error for any method) - NwcErrorCode enum with UNSUPPORTED_ENCRYPTION New event kinds: - NwcInfoEvent (kind 13194) - wallet service capabilities - NwcNotificationEvent (kind 23197) - NIP-44 encrypted notifications - Legacy kind 23196 support for NIP-04 notifications New data models: - NwcTransaction (shared invoice/payment object) - TlvRecord (for keysend TLV records) - Notification types: payment_received, payment_sent, hold_invoice_accepted - HoldInvoiceAcceptedData with settle_deadline Tags (following NIP-88 pattern): - EncryptionTag for encryption scheme negotiation - NotificationsTag for supported notification types Updated: - LnZapPaymentRequestEvent: generic createRequest() with NIP-44 support - URI parser: lud16 parameter support - Jackson deserializers: all methods routed correctly - EventFactory: new kinds registered https://claude.ai/code/session_01JFogiwyR4CPVP8cRznqahJ
This commit is contained in:
+30
-8
@@ -52,25 +52,47 @@ class LnZapPaymentRequestEvent(
|
|||||||
return OptimizedJsonMapper.fromJsonTo<Request>(jsonText)
|
return OptimizedJsonMapper.fromJsonTo<Request>(jsonText)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
fun encryptionScheme() = tags.firstOrNull { it.size > 1 && it[0] == "encryption" }?.get(1)
|
||||||
|
|
||||||
companion object {
|
companion object {
|
||||||
const val KIND = 23194
|
const val KIND = 23194
|
||||||
const val ALT = "Zap payment request"
|
const val ALT = "NWC request"
|
||||||
|
|
||||||
suspend fun create(
|
suspend fun create(
|
||||||
lnInvoice: String,
|
lnInvoice: String,
|
||||||
walletServicePubkey: String,
|
walletServicePubkey: String,
|
||||||
signer: NostrSigner,
|
signer: NostrSigner,
|
||||||
createdAt: Long = TimeUtils.now(),
|
createdAt: Long = TimeUtils.now(),
|
||||||
): LnZapPaymentRequestEvent {
|
): LnZapPaymentRequestEvent =
|
||||||
val serializedRequest = OptimizedJsonMapper.toJson(PayInvoiceMethod.create(lnInvoice))
|
createRequest(
|
||||||
|
PayInvoiceMethod.create(lnInvoice),
|
||||||
|
walletServicePubkey,
|
||||||
|
signer,
|
||||||
|
createdAt,
|
||||||
|
)
|
||||||
|
|
||||||
val tags = arrayOf(arrayOf("p", walletServicePubkey), AltTag.assemble(ALT))
|
suspend fun createRequest(
|
||||||
|
request: Request,
|
||||||
|
walletServicePubkey: String,
|
||||||
|
signer: NostrSigner,
|
||||||
|
createdAt: Long = TimeUtils.now(),
|
||||||
|
useNip44: Boolean = false,
|
||||||
|
): LnZapPaymentRequestEvent {
|
||||||
|
val serializedRequest = OptimizedJsonMapper.toJson(request)
|
||||||
|
|
||||||
|
val tags =
|
||||||
|
if (useNip44) {
|
||||||
|
arrayOf(arrayOf("p", walletServicePubkey), AltTag.assemble(ALT), arrayOf("encryption", "nip44_v2"))
|
||||||
|
} else {
|
||||||
|
arrayOf(arrayOf("p", walletServicePubkey), AltTag.assemble(ALT))
|
||||||
|
}
|
||||||
|
|
||||||
val encrypted =
|
val encrypted =
|
||||||
signer.nip04Encrypt(
|
if (useNip44) {
|
||||||
serializedRequest,
|
signer.nip44Encrypt(serializedRequest, walletServicePubkey)
|
||||||
walletServicePubkey,
|
} else {
|
||||||
)
|
signer.nip04Encrypt(serializedRequest, walletServicePubkey)
|
||||||
|
}
|
||||||
|
|
||||||
return signer.sign(createdAt, KIND, tags, encrypted)
|
return signer.sign(createdAt, KIND, tags, encrypted)
|
||||||
}
|
}
|
||||||
|
|||||||
+7
-4
@@ -30,11 +30,10 @@ import com.vitorpamplona.quartz.utils.UriParser
|
|||||||
import kotlinx.coroutines.CancellationException
|
import kotlinx.coroutines.CancellationException
|
||||||
import kotlinx.serialization.Serializable
|
import kotlinx.serialization.Serializable
|
||||||
|
|
||||||
// Rename to the corect nip number when ready.
|
|
||||||
class Nip47WalletConnect {
|
class Nip47WalletConnect {
|
||||||
companion object {
|
companion object {
|
||||||
fun parse(uri: String): Nip47URINorm {
|
fun parse(uri: String): Nip47URINorm {
|
||||||
// nostrwalletconnect://b889ff5b1513b641e2a139f661a661364979c5beee91842f8f0ef42ab558e9d4?relay=wss%3A%2F%2Frelay.damus.io&metadata=%7B%22name%22%3A%22Example%22%7D
|
// nostr+walletconnect://b889ff5b...?relay=wss%3A%2F%2Frelay.damus.io&secret=...&lud16=user@example.com
|
||||||
|
|
||||||
val url = UriParser(uri)
|
val url = UriParser(uri)
|
||||||
|
|
||||||
@@ -55,8 +54,9 @@ class Nip47WalletConnect {
|
|||||||
val relay = url.getQueryParameter("relay") ?: throw IllegalArgumentException("Relay cannot be null")
|
val relay = url.getQueryParameter("relay") ?: throw IllegalArgumentException("Relay cannot be null")
|
||||||
val relayNorm = RelayUrlNormalizer.normalizeOrNull(relay) ?: throw IllegalArgumentException("Invalid relay Url")
|
val relayNorm = RelayUrlNormalizer.normalizeOrNull(relay) ?: throw IllegalArgumentException("Invalid relay Url")
|
||||||
val secret = url.getQueryParameter("secret")
|
val secret = url.getQueryParameter("secret")
|
||||||
|
val lud16 = url.getQueryParameter("lud16")
|
||||||
|
|
||||||
return Nip47URINorm(pubkeyHex, relayNorm, secret)
|
return Nip47URINorm(pubkeyHex, relayNorm, secret, lud16)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -65,6 +65,7 @@ class Nip47WalletConnect {
|
|||||||
val pubKeyHex: HexKey,
|
val pubKeyHex: HexKey,
|
||||||
val relayUri: String,
|
val relayUri: String,
|
||||||
val secret: HexKey?,
|
val secret: HexKey?,
|
||||||
|
val lud16: String? = null,
|
||||||
) {
|
) {
|
||||||
fun normalize(): Nip47URINorm? =
|
fun normalize(): Nip47URINorm? =
|
||||||
RelayUrlNormalizer.normalizeOrNull(relayUri)?.let {
|
RelayUrlNormalizer.normalizeOrNull(relayUri)?.let {
|
||||||
@@ -72,6 +73,7 @@ class Nip47WalletConnect {
|
|||||||
pubKeyHex,
|
pubKeyHex,
|
||||||
it,
|
it,
|
||||||
secret,
|
secret,
|
||||||
|
lud16,
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -86,7 +88,8 @@ class Nip47WalletConnect {
|
|||||||
val pubKeyHex: HexKey,
|
val pubKeyHex: HexKey,
|
||||||
val relayUri: NormalizedRelayUrl,
|
val relayUri: NormalizedRelayUrl,
|
||||||
val secret: HexKey?,
|
val secret: HexKey?,
|
||||||
|
val lud16: String? = null,
|
||||||
) {
|
) {
|
||||||
fun denormalize(): Nip47URI? = Nip47URI(pubKeyHex, relayUri.url, secret)
|
fun denormalize(): Nip47URI? = Nip47URI(pubKeyHex, relayUri.url, secret, lud16)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
+59
@@ -0,0 +1,59 @@
|
|||||||
|
/*
|
||||||
|
* Copyright (c) 2025 Vitor Pamplona
|
||||||
|
*
|
||||||
|
* Permission is hereby granted, free of charge, to any person obtaining a copy of
|
||||||
|
* this software and associated documentation files (the "Software"), to deal in
|
||||||
|
* the Software without restriction, including without limitation the rights to use,
|
||||||
|
* copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the
|
||||||
|
* Software, and to permit persons to whom the Software is furnished to do so,
|
||||||
|
* subject to the following conditions:
|
||||||
|
*
|
||||||
|
* The above copyright notice and this permission notice shall be included in all
|
||||||
|
* copies or substantial portions of the Software.
|
||||||
|
*
|
||||||
|
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||||
|
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS
|
||||||
|
* FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR
|
||||||
|
* COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN
|
||||||
|
* AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
|
||||||
|
* WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
|
||||||
|
*/
|
||||||
|
package com.vitorpamplona.quartz.nip47WalletConnect
|
||||||
|
|
||||||
|
import com.vitorpamplona.quartz.nip01Core.core.OptimizedSerializable
|
||||||
|
|
||||||
|
object NwcNotificationType {
|
||||||
|
const val PAYMENT_RECEIVED = "payment_received"
|
||||||
|
const val PAYMENT_SENT = "payment_sent"
|
||||||
|
const val HOLD_INVOICE_ACCEPTED = "hold_invoice_accepted"
|
||||||
|
}
|
||||||
|
|
||||||
|
// NOTIFICATION OBJECTS
|
||||||
|
abstract class Notification(
|
||||||
|
val notification_type: String,
|
||||||
|
) : OptimizedSerializable
|
||||||
|
|
||||||
|
// payment_received notification
|
||||||
|
class PaymentReceivedNotification(
|
||||||
|
val notification: NwcTransaction? = null,
|
||||||
|
) : Notification(NwcNotificationType.PAYMENT_RECEIVED)
|
||||||
|
|
||||||
|
// payment_sent notification
|
||||||
|
class PaymentSentNotification(
|
||||||
|
val notification: NwcTransaction? = null,
|
||||||
|
) : Notification(NwcNotificationType.PAYMENT_SENT)
|
||||||
|
|
||||||
|
// hold_invoice_accepted notification
|
||||||
|
class HoldInvoiceAcceptedNotification(
|
||||||
|
val notification: HoldInvoiceAcceptedData? = null,
|
||||||
|
) : Notification(NwcNotificationType.HOLD_INVOICE_ACCEPTED)
|
||||||
|
|
||||||
|
class HoldInvoiceAcceptedData(
|
||||||
|
var type: String? = null,
|
||||||
|
var invoice: String? = null,
|
||||||
|
var payment_hash: String? = null,
|
||||||
|
var amount: Long? = null,
|
||||||
|
var created_at: Long? = null,
|
||||||
|
var expires_at: Long? = null,
|
||||||
|
var settle_deadline: Long? = null,
|
||||||
|
)
|
||||||
+39
@@ -0,0 +1,39 @@
|
|||||||
|
/*
|
||||||
|
* Copyright (c) 2025 Vitor Pamplona
|
||||||
|
*
|
||||||
|
* Permission is hereby granted, free of charge, to any person obtaining a copy of
|
||||||
|
* this software and associated documentation files (the "Software"), to deal in
|
||||||
|
* the Software without restriction, including without limitation the rights to use,
|
||||||
|
* copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the
|
||||||
|
* Software, and to permit persons to whom the Software is furnished to do so,
|
||||||
|
* subject to the following conditions:
|
||||||
|
*
|
||||||
|
* The above copyright notice and this permission notice shall be included in all
|
||||||
|
* copies or substantial portions of the Software.
|
||||||
|
*
|
||||||
|
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||||
|
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS
|
||||||
|
* FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR
|
||||||
|
* COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN
|
||||||
|
* AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
|
||||||
|
* WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
|
||||||
|
*/
|
||||||
|
package com.vitorpamplona.quartz.nip47WalletConnect
|
||||||
|
|
||||||
|
enum class NwcErrorCode {
|
||||||
|
RATE_LIMITED,
|
||||||
|
NOT_IMPLEMENTED,
|
||||||
|
INSUFFICIENT_BALANCE,
|
||||||
|
PAYMENT_FAILED,
|
||||||
|
QUOTA_EXCEEDED,
|
||||||
|
RESTRICTED,
|
||||||
|
UNAUTHORIZED,
|
||||||
|
INTERNAL,
|
||||||
|
UNSUPPORTED_ENCRYPTION,
|
||||||
|
OTHER,
|
||||||
|
}
|
||||||
|
|
||||||
|
class NwcError(
|
||||||
|
var code: NwcErrorCode? = null,
|
||||||
|
var message: String? = null,
|
||||||
|
)
|
||||||
+69
@@ -0,0 +1,69 @@
|
|||||||
|
/*
|
||||||
|
* Copyright (c) 2025 Vitor Pamplona
|
||||||
|
*
|
||||||
|
* Permission is hereby granted, free of charge, to any person obtaining a copy of
|
||||||
|
* this software and associated documentation files (the "Software"), to deal in
|
||||||
|
* the Software without restriction, including without limitation the rights to use,
|
||||||
|
* copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the
|
||||||
|
* Software, and to permit persons to whom the Software is furnished to do so,
|
||||||
|
* subject to the following conditions:
|
||||||
|
*
|
||||||
|
* The above copyright notice and this permission notice shall be included in all
|
||||||
|
* copies or substantial portions of the Software.
|
||||||
|
*
|
||||||
|
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||||
|
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS
|
||||||
|
* FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR
|
||||||
|
* COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN
|
||||||
|
* AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
|
||||||
|
* WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
|
||||||
|
*/
|
||||||
|
package com.vitorpamplona.quartz.nip47WalletConnect
|
||||||
|
|
||||||
|
import androidx.compose.runtime.Immutable
|
||||||
|
import com.vitorpamplona.quartz.nip01Core.core.Event
|
||||||
|
import com.vitorpamplona.quartz.nip01Core.core.HexKey
|
||||||
|
import com.vitorpamplona.quartz.nip01Core.core.TagArrayBuilder
|
||||||
|
import com.vitorpamplona.quartz.nip01Core.signers.eventTemplate
|
||||||
|
import com.vitorpamplona.quartz.nip31Alts.alt
|
||||||
|
import com.vitorpamplona.quartz.nip47WalletConnect.tags.EncryptionTag
|
||||||
|
import com.vitorpamplona.quartz.nip47WalletConnect.tags.NotificationsTag
|
||||||
|
import com.vitorpamplona.quartz.utils.TimeUtils
|
||||||
|
|
||||||
|
@Immutable
|
||||||
|
class NwcInfoEvent(
|
||||||
|
id: HexKey,
|
||||||
|
pubKey: HexKey,
|
||||||
|
createdAt: Long,
|
||||||
|
tags: Array<Array<String>>,
|
||||||
|
content: String,
|
||||||
|
sig: HexKey,
|
||||||
|
) : Event(id, pubKey, createdAt, KIND, tags, content, sig) {
|
||||||
|
fun capabilities(): List<String> = content.split(" ").filter { it.isNotBlank() }
|
||||||
|
|
||||||
|
fun supportsMethod(method: String): Boolean = capabilities().contains(method)
|
||||||
|
|
||||||
|
fun supportsNotifications(): Boolean = capabilities().contains("notifications")
|
||||||
|
|
||||||
|
fun encryptionSchemes() = tags.mapNotNull(EncryptionTag::parse).flatten()
|
||||||
|
|
||||||
|
fun notificationTypes() = tags.mapNotNull(NotificationsTag::parse).flatten()
|
||||||
|
|
||||||
|
companion object {
|
||||||
|
const val KIND = 13194
|
||||||
|
const val ALT_DESCRIPTION = "Wallet service info"
|
||||||
|
|
||||||
|
fun build(
|
||||||
|
capabilities: List<String>,
|
||||||
|
encryptionSchemes: List<String>? = null,
|
||||||
|
notificationTypes: List<String>? = null,
|
||||||
|
createdAt: Long = TimeUtils.now(),
|
||||||
|
initializer: TagArrayBuilder<NwcInfoEvent>.() -> Unit = {},
|
||||||
|
) = eventTemplate(KIND, capabilities.joinToString(" "), createdAt) {
|
||||||
|
alt(ALT_DESCRIPTION)
|
||||||
|
encryptionSchemes?.let { addUnique(EncryptionTag.assemble(it)) }
|
||||||
|
notificationTypes?.let { addUnique(NotificationsTag.assemble(it)) }
|
||||||
|
initializer()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,34 @@
|
|||||||
|
/*
|
||||||
|
* Copyright (c) 2025 Vitor Pamplona
|
||||||
|
*
|
||||||
|
* Permission is hereby granted, free of charge, to any person obtaining a copy of
|
||||||
|
* this software and associated documentation files (the "Software"), to deal in
|
||||||
|
* the Software without restriction, including without limitation the rights to use,
|
||||||
|
* copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the
|
||||||
|
* Software, and to permit persons to whom the Software is furnished to do so,
|
||||||
|
* subject to the following conditions:
|
||||||
|
*
|
||||||
|
* The above copyright notice and this permission notice shall be included in all
|
||||||
|
* copies or substantial portions of the Software.
|
||||||
|
*
|
||||||
|
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||||
|
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS
|
||||||
|
* FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR
|
||||||
|
* COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN
|
||||||
|
* AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
|
||||||
|
* WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
|
||||||
|
*/
|
||||||
|
package com.vitorpamplona.quartz.nip47WalletConnect
|
||||||
|
|
||||||
|
object NwcMethod {
|
||||||
|
const val PAY_INVOICE = "pay_invoice"
|
||||||
|
const val PAY_KEYSEND = "pay_keysend"
|
||||||
|
const val MAKE_INVOICE = "make_invoice"
|
||||||
|
const val LOOKUP_INVOICE = "lookup_invoice"
|
||||||
|
const val LIST_TRANSACTIONS = "list_transactions"
|
||||||
|
const val GET_BALANCE = "get_balance"
|
||||||
|
const val GET_INFO = "get_info"
|
||||||
|
const val MAKE_HOLD_INVOICE = "make_hold_invoice"
|
||||||
|
const val CANCEL_HOLD_INVOICE = "cancel_hold_invoice"
|
||||||
|
const val SETTLE_HOLD_INVOICE = "settle_hold_invoice"
|
||||||
|
}
|
||||||
+58
@@ -0,0 +1,58 @@
|
|||||||
|
/*
|
||||||
|
* Copyright (c) 2025 Vitor Pamplona
|
||||||
|
*
|
||||||
|
* Permission is hereby granted, free of charge, to any person obtaining a copy of
|
||||||
|
* this software and associated documentation files (the "Software"), to deal in
|
||||||
|
* the Software without restriction, including without limitation the rights to use,
|
||||||
|
* copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the
|
||||||
|
* Software, and to permit persons to whom the Software is furnished to do so,
|
||||||
|
* subject to the following conditions:
|
||||||
|
*
|
||||||
|
* The above copyright notice and this permission notice shall be included in all
|
||||||
|
* copies or substantial portions of the Software.
|
||||||
|
*
|
||||||
|
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||||
|
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS
|
||||||
|
* FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR
|
||||||
|
* COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN
|
||||||
|
* AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
|
||||||
|
* WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
|
||||||
|
*/
|
||||||
|
package com.vitorpamplona.quartz.nip47WalletConnect
|
||||||
|
|
||||||
|
import androidx.compose.runtime.Immutable
|
||||||
|
import com.vitorpamplona.quartz.nip01Core.core.Event
|
||||||
|
import com.vitorpamplona.quartz.nip01Core.core.HexKey
|
||||||
|
import com.vitorpamplona.quartz.nip01Core.core.OptimizedJsonMapper
|
||||||
|
import com.vitorpamplona.quartz.nip01Core.signers.NostrSigner
|
||||||
|
import com.vitorpamplona.quartz.nip01Core.signers.SignerExceptions
|
||||||
|
|
||||||
|
@Immutable
|
||||||
|
class NwcNotificationEvent(
|
||||||
|
id: HexKey,
|
||||||
|
pubKey: HexKey,
|
||||||
|
createdAt: Long,
|
||||||
|
tags: Array<Array<String>>,
|
||||||
|
content: String,
|
||||||
|
sig: HexKey,
|
||||||
|
) : Event(id, pubKey, createdAt, KIND, tags, content, sig) {
|
||||||
|
override fun isContentEncoded() = true
|
||||||
|
|
||||||
|
fun clientPubKey() = tags.firstOrNull { it.size > 1 && it[0] == "p" }?.get(1)
|
||||||
|
|
||||||
|
fun talkingWith(oneSideHex: String): HexKey = if (pubKey == oneSideHex) clientPubKey() ?: pubKey else pubKey
|
||||||
|
|
||||||
|
fun canDecrypt(signer: NostrSigner) = pubKey == signer.pubKey || clientPubKey() == signer.pubKey
|
||||||
|
|
||||||
|
suspend fun decryptNotification(signer: NostrSigner): Notification {
|
||||||
|
if (!canDecrypt(signer)) throw SignerExceptions.UnauthorizedDecryptionException()
|
||||||
|
val jsonText = signer.nip44Decrypt(content, talkingWith(signer.pubKey))
|
||||||
|
return OptimizedJsonMapper.fromJsonTo<Notification>(jsonText)
|
||||||
|
}
|
||||||
|
|
||||||
|
companion object {
|
||||||
|
const val KIND = 23197
|
||||||
|
const val LEGACY_KIND = 23196
|
||||||
|
const val ALT = "Wallet notification"
|
||||||
|
}
|
||||||
|
}
|
||||||
+42
@@ -0,0 +1,42 @@
|
|||||||
|
/*
|
||||||
|
* Copyright (c) 2025 Vitor Pamplona
|
||||||
|
*
|
||||||
|
* Permission is hereby granted, free of charge, to any person obtaining a copy of
|
||||||
|
* this software and associated documentation files (the "Software"), to deal in
|
||||||
|
* the Software without restriction, including without limitation the rights to use,
|
||||||
|
* copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the
|
||||||
|
* Software, and to permit persons to whom the Software is furnished to do so,
|
||||||
|
* subject to the following conditions:
|
||||||
|
*
|
||||||
|
* The above copyright notice and this permission notice shall be included in all
|
||||||
|
* copies or substantial portions of the Software.
|
||||||
|
*
|
||||||
|
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||||
|
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS
|
||||||
|
* FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR
|
||||||
|
* COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN
|
||||||
|
* AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
|
||||||
|
* WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
|
||||||
|
*/
|
||||||
|
package com.vitorpamplona.quartz.nip47WalletConnect
|
||||||
|
|
||||||
|
class NwcTransaction(
|
||||||
|
var type: String? = null,
|
||||||
|
var state: String? = null,
|
||||||
|
var invoice: String? = null,
|
||||||
|
var description: String? = null,
|
||||||
|
var description_hash: String? = null,
|
||||||
|
var preimage: String? = null,
|
||||||
|
var payment_hash: String? = null,
|
||||||
|
var amount: Long? = null,
|
||||||
|
var fees_paid: Long? = null,
|
||||||
|
var created_at: Long? = null,
|
||||||
|
var expires_at: Long? = null,
|
||||||
|
var settled_at: Long? = null,
|
||||||
|
var metadata: Any? = null,
|
||||||
|
)
|
||||||
|
|
||||||
|
class TlvRecord(
|
||||||
|
var type: Long? = null,
|
||||||
|
var value: String? = null,
|
||||||
|
)
|
||||||
+161
-2
@@ -27,15 +27,174 @@ abstract class Request(
|
|||||||
var method: String? = null,
|
var method: String? = null,
|
||||||
) : OptimizedSerializable
|
) : OptimizedSerializable
|
||||||
|
|
||||||
// PayInvoice Call
|
// pay_invoice
|
||||||
class PayInvoiceParams(
|
class PayInvoiceParams(
|
||||||
var invoice: String? = null,
|
var invoice: String? = null,
|
||||||
|
var amount: Long? = null,
|
||||||
|
var metadata: Any? = null,
|
||||||
)
|
)
|
||||||
|
|
||||||
class PayInvoiceMethod(
|
class PayInvoiceMethod(
|
||||||
var params: PayInvoiceParams? = null,
|
var params: PayInvoiceParams? = null,
|
||||||
) : Request("pay_invoice") {
|
) : Request(NwcMethod.PAY_INVOICE) {
|
||||||
companion object {
|
companion object {
|
||||||
fun create(bolt11: String): PayInvoiceMethod = PayInvoiceMethod(PayInvoiceParams(bolt11))
|
fun create(bolt11: String): PayInvoiceMethod = PayInvoiceMethod(PayInvoiceParams(bolt11))
|
||||||
|
|
||||||
|
fun create(
|
||||||
|
bolt11: String,
|
||||||
|
amount: Long,
|
||||||
|
): PayInvoiceMethod = PayInvoiceMethod(PayInvoiceParams(bolt11, amount))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// pay_keysend
|
||||||
|
class PayKeysendParams(
|
||||||
|
var amount: Long? = null,
|
||||||
|
var pubkey: String? = null,
|
||||||
|
var preimage: String? = null,
|
||||||
|
var tlv_records: List<TlvRecord>? = null,
|
||||||
|
)
|
||||||
|
|
||||||
|
class PayKeysendMethod(
|
||||||
|
var params: PayKeysendParams? = null,
|
||||||
|
) : Request(NwcMethod.PAY_KEYSEND) {
|
||||||
|
companion object {
|
||||||
|
fun create(
|
||||||
|
amount: Long,
|
||||||
|
pubkey: String,
|
||||||
|
preimage: String? = null,
|
||||||
|
tlvRecords: List<TlvRecord>? = null,
|
||||||
|
): PayKeysendMethod = PayKeysendMethod(PayKeysendParams(amount, pubkey, preimage, tlvRecords))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// make_invoice
|
||||||
|
class MakeInvoiceParams(
|
||||||
|
var amount: Long? = null,
|
||||||
|
var description: String? = null,
|
||||||
|
var description_hash: String? = null,
|
||||||
|
var expiry: Long? = null,
|
||||||
|
var metadata: Any? = null,
|
||||||
|
)
|
||||||
|
|
||||||
|
class MakeInvoiceMethod(
|
||||||
|
var params: MakeInvoiceParams? = null,
|
||||||
|
) : Request(NwcMethod.MAKE_INVOICE) {
|
||||||
|
companion object {
|
||||||
|
fun create(
|
||||||
|
amount: Long,
|
||||||
|
description: String? = null,
|
||||||
|
descriptionHash: String? = null,
|
||||||
|
expiry: Long? = null,
|
||||||
|
): MakeInvoiceMethod = MakeInvoiceMethod(MakeInvoiceParams(amount, description, descriptionHash, expiry))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// lookup_invoice
|
||||||
|
class LookupInvoiceParams(
|
||||||
|
var payment_hash: String? = null,
|
||||||
|
var invoice: String? = null,
|
||||||
|
)
|
||||||
|
|
||||||
|
class LookupInvoiceMethod(
|
||||||
|
var params: LookupInvoiceParams? = null,
|
||||||
|
) : Request(NwcMethod.LOOKUP_INVOICE) {
|
||||||
|
companion object {
|
||||||
|
fun createByHash(paymentHash: String): LookupInvoiceMethod = LookupInvoiceMethod(LookupInvoiceParams(payment_hash = paymentHash))
|
||||||
|
|
||||||
|
fun createByInvoice(invoice: String): LookupInvoiceMethod = LookupInvoiceMethod(LookupInvoiceParams(invoice = invoice))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// list_transactions
|
||||||
|
class ListTransactionsParams(
|
||||||
|
var from: Long? = null,
|
||||||
|
var until: Long? = null,
|
||||||
|
var limit: Int? = null,
|
||||||
|
var offset: Int? = null,
|
||||||
|
var unpaid: Boolean? = null,
|
||||||
|
var type: String? = null,
|
||||||
|
)
|
||||||
|
|
||||||
|
class ListTransactionsMethod(
|
||||||
|
var params: ListTransactionsParams? = null,
|
||||||
|
) : Request(NwcMethod.LIST_TRANSACTIONS) {
|
||||||
|
companion object {
|
||||||
|
fun create(
|
||||||
|
from: Long? = null,
|
||||||
|
until: Long? = null,
|
||||||
|
limit: Int? = null,
|
||||||
|
offset: Int? = null,
|
||||||
|
unpaid: Boolean? = null,
|
||||||
|
type: String? = null,
|
||||||
|
): ListTransactionsMethod = ListTransactionsMethod(ListTransactionsParams(from, until, limit, offset, unpaid, type))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// get_balance
|
||||||
|
class GetBalanceMethod : Request(NwcMethod.GET_BALANCE) {
|
||||||
|
companion object {
|
||||||
|
fun create(): GetBalanceMethod = GetBalanceMethod()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// get_info
|
||||||
|
class GetInfoMethod : Request(NwcMethod.GET_INFO) {
|
||||||
|
companion object {
|
||||||
|
fun create(): GetInfoMethod = GetInfoMethod()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// make_hold_invoice
|
||||||
|
class MakeHoldInvoiceParams(
|
||||||
|
var amount: Long? = null,
|
||||||
|
var description: String? = null,
|
||||||
|
var description_hash: String? = null,
|
||||||
|
var expiry: Long? = null,
|
||||||
|
var payment_hash: String? = null,
|
||||||
|
var min_cltv_expiry_delta: Int? = null,
|
||||||
|
)
|
||||||
|
|
||||||
|
class MakeHoldInvoiceMethod(
|
||||||
|
var params: MakeHoldInvoiceParams? = null,
|
||||||
|
) : Request(NwcMethod.MAKE_HOLD_INVOICE) {
|
||||||
|
companion object {
|
||||||
|
fun create(
|
||||||
|
amount: Long,
|
||||||
|
paymentHash: String,
|
||||||
|
description: String? = null,
|
||||||
|
descriptionHash: String? = null,
|
||||||
|
expiry: Long? = null,
|
||||||
|
minCltvExpiryDelta: Int? = null,
|
||||||
|
): MakeHoldInvoiceMethod =
|
||||||
|
MakeHoldInvoiceMethod(
|
||||||
|
MakeHoldInvoiceParams(amount, description, descriptionHash, expiry, paymentHash, minCltvExpiryDelta),
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// cancel_hold_invoice
|
||||||
|
class CancelHoldInvoiceParams(
|
||||||
|
var payment_hash: String? = null,
|
||||||
|
)
|
||||||
|
|
||||||
|
class CancelHoldInvoiceMethod(
|
||||||
|
var params: CancelHoldInvoiceParams? = null,
|
||||||
|
) : Request(NwcMethod.CANCEL_HOLD_INVOICE) {
|
||||||
|
companion object {
|
||||||
|
fun create(paymentHash: String): CancelHoldInvoiceMethod = CancelHoldInvoiceMethod(CancelHoldInvoiceParams(paymentHash))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// settle_hold_invoice
|
||||||
|
class SettleHoldInvoiceParams(
|
||||||
|
var preimage: String? = null,
|
||||||
|
)
|
||||||
|
|
||||||
|
class SettleHoldInvoiceMethod(
|
||||||
|
var params: SettleHoldInvoiceParams? = null,
|
||||||
|
) : Request(NwcMethod.SETTLE_HOLD_INVOICE) {
|
||||||
|
companion object {
|
||||||
|
fun create(preimage: String): SettleHoldInvoiceMethod = SettleHoldInvoiceMethod(SettleHoldInvoiceParams(preimage))
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
+80
-32
@@ -27,49 +27,97 @@ abstract class Response(
|
|||||||
val resultType: String,
|
val resultType: String,
|
||||||
) : OptimizedSerializable
|
) : OptimizedSerializable
|
||||||
|
|
||||||
// PayInvoice Call
|
// Generic error response for any method
|
||||||
|
class NwcErrorResponse(
|
||||||
|
resultType: String,
|
||||||
|
val error: NwcError? = null,
|
||||||
|
) : Response(resultType)
|
||||||
|
|
||||||
|
// pay_invoice success response
|
||||||
class PayInvoiceSuccessResponse(
|
class PayInvoiceSuccessResponse(
|
||||||
val result: PayInvoiceResultParams? = null,
|
val result: PayInvoiceResultParams? = null,
|
||||||
) : Response("pay_invoice") {
|
) : Response(NwcMethod.PAY_INVOICE) {
|
||||||
class PayInvoiceResultParams(
|
class PayInvoiceResultParams(
|
||||||
val preimage: String? = null,
|
val preimage: String? = null,
|
||||||
|
val fees_paid: Long? = null,
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// pay_invoice error response (kept for backward compatibility)
|
||||||
class PayInvoiceErrorResponse(
|
class PayInvoiceErrorResponse(
|
||||||
val error: PayInvoiceErrorParams? = null,
|
val error: PayInvoiceErrorParams? = null,
|
||||||
) : Response("pay_invoice") {
|
) : Response(NwcMethod.PAY_INVOICE) {
|
||||||
class PayInvoiceErrorParams(
|
class PayInvoiceErrorParams(
|
||||||
val code: ErrorType? = null,
|
val code: NwcErrorCode? = null,
|
||||||
val message: String? = null,
|
val message: String? = null,
|
||||||
)
|
)
|
||||||
|
|
||||||
enum class ErrorType {
|
|
||||||
RATE_LIMITED,
|
|
||||||
|
|
||||||
// The client is sending commands too fast. It should retry in a few seconds.
|
|
||||||
NOT_IMPLEMENTED,
|
|
||||||
|
|
||||||
// The command is not known or is intentionally not implemented.
|
|
||||||
INSUFFICIENT_BALANCE,
|
|
||||||
|
|
||||||
// The command is not known or is intentionally not implemented.
|
|
||||||
PAYMENT_FAILED,
|
|
||||||
|
|
||||||
// The wallet does not have enough funds to cover a fee reserve or the payment amount.
|
|
||||||
QUOTA_EXCEEDED,
|
|
||||||
|
|
||||||
// The wallet has exceeded its spending quota.
|
|
||||||
RESTRICTED,
|
|
||||||
|
|
||||||
// This public key is not allowed to do this operation.
|
|
||||||
UNAUTHORIZED,
|
|
||||||
|
|
||||||
// This public key has no wallet connected.
|
|
||||||
INTERNAL,
|
|
||||||
|
|
||||||
// An internal error.
|
|
||||||
OTHER, // Other error.
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// pay_keysend success response
|
||||||
|
class PayKeysendSuccessResponse(
|
||||||
|
val result: PayKeysendResult? = null,
|
||||||
|
) : Response(NwcMethod.PAY_KEYSEND) {
|
||||||
|
class PayKeysendResult(
|
||||||
|
val preimage: String? = null,
|
||||||
|
val fees_paid: Long? = null,
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
// make_invoice success response
|
||||||
|
class MakeInvoiceSuccessResponse(
|
||||||
|
val result: NwcTransaction? = null,
|
||||||
|
) : Response(NwcMethod.MAKE_INVOICE)
|
||||||
|
|
||||||
|
// lookup_invoice success response
|
||||||
|
class LookupInvoiceSuccessResponse(
|
||||||
|
val result: NwcTransaction? = null,
|
||||||
|
) : Response(NwcMethod.LOOKUP_INVOICE)
|
||||||
|
|
||||||
|
// list_transactions success response
|
||||||
|
class ListTransactionsSuccessResponse(
|
||||||
|
val result: ListTransactionsResult? = null,
|
||||||
|
) : Response(NwcMethod.LIST_TRANSACTIONS) {
|
||||||
|
class ListTransactionsResult(
|
||||||
|
val transactions: List<NwcTransaction>? = null,
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
// get_balance success response
|
||||||
|
class GetBalanceSuccessResponse(
|
||||||
|
val result: GetBalanceResult? = null,
|
||||||
|
) : Response(NwcMethod.GET_BALANCE) {
|
||||||
|
class GetBalanceResult(
|
||||||
|
val balance: Long? = null,
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
// get_info success response
|
||||||
|
class GetInfoSuccessResponse(
|
||||||
|
val result: GetInfoResult? = null,
|
||||||
|
) : Response(NwcMethod.GET_INFO) {
|
||||||
|
class GetInfoResult(
|
||||||
|
val alias: String? = null,
|
||||||
|
val color: String? = null,
|
||||||
|
val pubkey: String? = null,
|
||||||
|
val network: String? = null,
|
||||||
|
val block_height: Long? = null,
|
||||||
|
val block_hash: String? = null,
|
||||||
|
val methods: List<String>? = null,
|
||||||
|
val notifications: List<String>? = null,
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
// make_hold_invoice success response
|
||||||
|
class MakeHoldInvoiceSuccessResponse(
|
||||||
|
val result: NwcTransaction? = null,
|
||||||
|
) : Response(NwcMethod.MAKE_HOLD_INVOICE)
|
||||||
|
|
||||||
|
// cancel_hold_invoice success response
|
||||||
|
class CancelHoldInvoiceSuccessResponse(
|
||||||
|
val result: Any? = null,
|
||||||
|
) : Response(NwcMethod.CANCEL_HOLD_INVOICE)
|
||||||
|
|
||||||
|
// settle_hold_invoice success response
|
||||||
|
class SettleHoldInvoiceSuccessResponse(
|
||||||
|
val result: Any? = null,
|
||||||
|
) : Response(NwcMethod.SETTLE_HOLD_INVOICE)
|
||||||
|
|||||||
+41
@@ -0,0 +1,41 @@
|
|||||||
|
/*
|
||||||
|
* Copyright (c) 2025 Vitor Pamplona
|
||||||
|
*
|
||||||
|
* Permission is hereby granted, free of charge, to any person obtaining a copy of
|
||||||
|
* this software and associated documentation files (the "Software"), to deal in
|
||||||
|
* the Software without restriction, including without limitation the rights to use,
|
||||||
|
* copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the
|
||||||
|
* Software, and to permit persons to whom the Software is furnished to do so,
|
||||||
|
* subject to the following conditions:
|
||||||
|
*
|
||||||
|
* The above copyright notice and this permission notice shall be included in all
|
||||||
|
* copies or substantial portions of the Software.
|
||||||
|
*
|
||||||
|
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||||
|
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS
|
||||||
|
* FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR
|
||||||
|
* COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN
|
||||||
|
* AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
|
||||||
|
* WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
|
||||||
|
*/
|
||||||
|
package com.vitorpamplona.quartz.nip47WalletConnect.tags
|
||||||
|
|
||||||
|
import com.vitorpamplona.quartz.nip01Core.core.has
|
||||||
|
import com.vitorpamplona.quartz.utils.ensure
|
||||||
|
|
||||||
|
class EncryptionTag {
|
||||||
|
companion object {
|
||||||
|
const val TAG_NAME = "encryption"
|
||||||
|
|
||||||
|
fun isTag(tag: Array<String>) = tag.has(1) && tag[0] == TAG_NAME && tag[1].isNotEmpty()
|
||||||
|
|
||||||
|
fun parse(tag: Array<String>): List<String>? {
|
||||||
|
ensure(tag.has(1)) { return null }
|
||||||
|
ensure(tag[0] == TAG_NAME) { return null }
|
||||||
|
ensure(tag[1].isNotEmpty()) { return null }
|
||||||
|
return tag.drop(1)
|
||||||
|
}
|
||||||
|
|
||||||
|
fun assemble(schemes: List<String>) = arrayOf(TAG_NAME, *schemes.toTypedArray())
|
||||||
|
}
|
||||||
|
}
|
||||||
+41
@@ -0,0 +1,41 @@
|
|||||||
|
/*
|
||||||
|
* Copyright (c) 2025 Vitor Pamplona
|
||||||
|
*
|
||||||
|
* Permission is hereby granted, free of charge, to any person obtaining a copy of
|
||||||
|
* this software and associated documentation files (the "Software"), to deal in
|
||||||
|
* the Software without restriction, including without limitation the rights to use,
|
||||||
|
* copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the
|
||||||
|
* Software, and to permit persons to whom the Software is furnished to do so,
|
||||||
|
* subject to the following conditions:
|
||||||
|
*
|
||||||
|
* The above copyright notice and this permission notice shall be included in all
|
||||||
|
* copies or substantial portions of the Software.
|
||||||
|
*
|
||||||
|
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||||
|
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS
|
||||||
|
* FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR
|
||||||
|
* COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN
|
||||||
|
* AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
|
||||||
|
* WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
|
||||||
|
*/
|
||||||
|
package com.vitorpamplona.quartz.nip47WalletConnect.tags
|
||||||
|
|
||||||
|
import com.vitorpamplona.quartz.nip01Core.core.has
|
||||||
|
import com.vitorpamplona.quartz.utils.ensure
|
||||||
|
|
||||||
|
class NotificationsTag {
|
||||||
|
companion object {
|
||||||
|
const val TAG_NAME = "notifications"
|
||||||
|
|
||||||
|
fun isTag(tag: Array<String>) = tag.has(1) && tag[0] == TAG_NAME && tag[1].isNotEmpty()
|
||||||
|
|
||||||
|
fun parse(tag: Array<String>): List<String>? {
|
||||||
|
ensure(tag.has(1)) { return null }
|
||||||
|
ensure(tag[0] == TAG_NAME) { return null }
|
||||||
|
ensure(tag[1].isNotEmpty()) { return null }
|
||||||
|
return tag.drop(1)
|
||||||
|
}
|
||||||
|
|
||||||
|
fun assemble(types: List<String>) = arrayOf(TAG_NAME, *types.toTypedArray())
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -78,6 +78,8 @@ import com.vitorpamplona.quartz.nip42RelayAuth.RelayAuthEvent
|
|||||||
import com.vitorpamplona.quartz.nip46RemoteSigner.NostrConnectEvent
|
import com.vitorpamplona.quartz.nip46RemoteSigner.NostrConnectEvent
|
||||||
import com.vitorpamplona.quartz.nip47WalletConnect.LnZapPaymentRequestEvent
|
import com.vitorpamplona.quartz.nip47WalletConnect.LnZapPaymentRequestEvent
|
||||||
import com.vitorpamplona.quartz.nip47WalletConnect.LnZapPaymentResponseEvent
|
import com.vitorpamplona.quartz.nip47WalletConnect.LnZapPaymentResponseEvent
|
||||||
|
import com.vitorpamplona.quartz.nip47WalletConnect.NwcInfoEvent
|
||||||
|
import com.vitorpamplona.quartz.nip47WalletConnect.NwcNotificationEvent
|
||||||
import com.vitorpamplona.quartz.nip50Search.SearchRelayListEvent
|
import com.vitorpamplona.quartz.nip50Search.SearchRelayListEvent
|
||||||
import com.vitorpamplona.quartz.nip51Lists.PinListEvent
|
import com.vitorpamplona.quartz.nip51Lists.PinListEvent
|
||||||
import com.vitorpamplona.quartz.nip51Lists.bookmarkList.BookmarkListEvent
|
import com.vitorpamplona.quartz.nip51Lists.bookmarkList.BookmarkListEvent
|
||||||
@@ -256,6 +258,9 @@ class EventFactory {
|
|||||||
LnZapEvent.KIND -> LnZapEvent(id, pubKey, createdAt, tags, content, sig)
|
LnZapEvent.KIND -> LnZapEvent(id, pubKey, createdAt, tags, content, sig)
|
||||||
LnZapPaymentRequestEvent.KIND -> LnZapPaymentRequestEvent(id, pubKey, createdAt, tags, content, sig)
|
LnZapPaymentRequestEvent.KIND -> LnZapPaymentRequestEvent(id, pubKey, createdAt, tags, content, sig)
|
||||||
LnZapPaymentResponseEvent.KIND -> LnZapPaymentResponseEvent(id, pubKey, createdAt, tags, content, sig)
|
LnZapPaymentResponseEvent.KIND -> LnZapPaymentResponseEvent(id, pubKey, createdAt, tags, content, sig)
|
||||||
|
NwcInfoEvent.KIND -> NwcInfoEvent(id, pubKey, createdAt, tags, content, sig)
|
||||||
|
NwcNotificationEvent.KIND -> NwcNotificationEvent(id, pubKey, createdAt, tags, content, sig)
|
||||||
|
NwcNotificationEvent.LEGACY_KIND -> NwcNotificationEvent(id, pubKey, createdAt, tags, content, sig)
|
||||||
LnZapPrivateEvent.KIND -> LnZapPrivateEvent(id, pubKey, createdAt, tags, content, sig)
|
LnZapPrivateEvent.KIND -> LnZapPrivateEvent(id, pubKey, createdAt, tags, content, sig)
|
||||||
LnZapRequestEvent.KIND -> LnZapRequestEvent(id, pubKey, createdAt, tags, content, sig)
|
LnZapRequestEvent.KIND -> LnZapRequestEvent(id, pubKey, createdAt, tags, content, sig)
|
||||||
LongTextNoteEvent.KIND -> LongTextNoteEvent(id, pubKey, createdAt, tags, content, sig)
|
LongTextNoteEvent.KIND -> LongTextNoteEvent(id, pubKey, createdAt, tags, content, sig)
|
||||||
|
|||||||
+3
@@ -52,8 +52,10 @@ import com.vitorpamplona.quartz.nip46RemoteSigner.jackson.BunkerRequestDeseriali
|
|||||||
import com.vitorpamplona.quartz.nip46RemoteSigner.jackson.BunkerRequestSerializer
|
import com.vitorpamplona.quartz.nip46RemoteSigner.jackson.BunkerRequestSerializer
|
||||||
import com.vitorpamplona.quartz.nip46RemoteSigner.jackson.BunkerResponseDeserializer
|
import com.vitorpamplona.quartz.nip46RemoteSigner.jackson.BunkerResponseDeserializer
|
||||||
import com.vitorpamplona.quartz.nip46RemoteSigner.jackson.BunkerResponseSerializer
|
import com.vitorpamplona.quartz.nip46RemoteSigner.jackson.BunkerResponseSerializer
|
||||||
|
import com.vitorpamplona.quartz.nip47WalletConnect.Notification
|
||||||
import com.vitorpamplona.quartz.nip47WalletConnect.Request
|
import com.vitorpamplona.quartz.nip47WalletConnect.Request
|
||||||
import com.vitorpamplona.quartz.nip47WalletConnect.Response
|
import com.vitorpamplona.quartz.nip47WalletConnect.Response
|
||||||
|
import com.vitorpamplona.quartz.nip47WalletConnect.jackson.NotificationDeserializer
|
||||||
import com.vitorpamplona.quartz.nip47WalletConnect.jackson.RequestDeserializer
|
import com.vitorpamplona.quartz.nip47WalletConnect.jackson.RequestDeserializer
|
||||||
import com.vitorpamplona.quartz.nip47WalletConnect.jackson.ResponseDeserializer
|
import com.vitorpamplona.quartz.nip47WalletConnect.jackson.ResponseDeserializer
|
||||||
import com.vitorpamplona.quartz.nip59Giftwrap.rumors.Rumor
|
import com.vitorpamplona.quartz.nip59Giftwrap.rumors.Rumor
|
||||||
@@ -93,6 +95,7 @@ class JacksonMapper {
|
|||||||
// nip 47
|
// nip 47
|
||||||
.addDeserializer(Response::class.java, ResponseDeserializer())
|
.addDeserializer(Response::class.java, ResponseDeserializer())
|
||||||
.addDeserializer(Request::class.java, RequestDeserializer())
|
.addDeserializer(Request::class.java, RequestDeserializer())
|
||||||
|
.addDeserializer(Notification::class.java, NotificationDeserializer())
|
||||||
// nip 46
|
// nip 46
|
||||||
.addDeserializer(BunkerMessage::class.java, BunkerMessageDeserializer())
|
.addDeserializer(BunkerMessage::class.java, BunkerMessageDeserializer())
|
||||||
.addSerializer(BunkerRequest::class.java, BunkerRequestSerializer())
|
.addSerializer(BunkerRequest::class.java, BunkerRequestSerializer())
|
||||||
|
|||||||
+49
@@ -0,0 +1,49 @@
|
|||||||
|
/*
|
||||||
|
* Copyright (c) 2025 Vitor Pamplona
|
||||||
|
*
|
||||||
|
* Permission is hereby granted, free of charge, to any person obtaining a copy of
|
||||||
|
* this software and associated documentation files (the "Software"), to deal in
|
||||||
|
* the Software without restriction, including without limitation the rights to use,
|
||||||
|
* copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the
|
||||||
|
* Software, and to permit persons to whom the Software is furnished to do so,
|
||||||
|
* subject to the following conditions:
|
||||||
|
*
|
||||||
|
* The above copyright notice and this permission notice shall be included in all
|
||||||
|
* copies or substantial portions of the Software.
|
||||||
|
*
|
||||||
|
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||||
|
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS
|
||||||
|
* FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR
|
||||||
|
* COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN
|
||||||
|
* AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
|
||||||
|
* WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
|
||||||
|
*/
|
||||||
|
package com.vitorpamplona.quartz.nip47WalletConnect.jackson
|
||||||
|
|
||||||
|
import com.fasterxml.jackson.core.JsonParser
|
||||||
|
import com.fasterxml.jackson.databind.DeserializationContext
|
||||||
|
import com.fasterxml.jackson.databind.JsonNode
|
||||||
|
import com.fasterxml.jackson.databind.deser.std.StdDeserializer
|
||||||
|
import com.vitorpamplona.quartz.nip47WalletConnect.HoldInvoiceAcceptedNotification
|
||||||
|
import com.vitorpamplona.quartz.nip47WalletConnect.Notification
|
||||||
|
import com.vitorpamplona.quartz.nip47WalletConnect.NwcNotificationType
|
||||||
|
import com.vitorpamplona.quartz.nip47WalletConnect.PaymentReceivedNotification
|
||||||
|
import com.vitorpamplona.quartz.nip47WalletConnect.PaymentSentNotification
|
||||||
|
import com.vitorpamplona.quartz.utils.asTextOrNull
|
||||||
|
|
||||||
|
class NotificationDeserializer : StdDeserializer<Notification>(Notification::class.java) {
|
||||||
|
override fun deserialize(
|
||||||
|
jp: JsonParser,
|
||||||
|
ctxt: DeserializationContext,
|
||||||
|
): Notification? {
|
||||||
|
val jsonObject: JsonNode = jp.codec.readTree(jp)
|
||||||
|
val notificationType = jsonObject.get("notification_type")?.asTextOrNull()
|
||||||
|
|
||||||
|
return when (notificationType) {
|
||||||
|
NwcNotificationType.PAYMENT_RECEIVED -> jp.codec.treeToValue(jsonObject, PaymentReceivedNotification::class.java)
|
||||||
|
NwcNotificationType.PAYMENT_SENT -> jp.codec.treeToValue(jsonObject, PaymentSentNotification::class.java)
|
||||||
|
NwcNotificationType.HOLD_INVOICE_ACCEPTED -> jp.codec.treeToValue(jsonObject, HoldInvoiceAcceptedNotification::class.java)
|
||||||
|
else -> null
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
+22
-3
@@ -24,8 +24,18 @@ import com.fasterxml.jackson.core.JsonParser
|
|||||||
import com.fasterxml.jackson.databind.DeserializationContext
|
import com.fasterxml.jackson.databind.DeserializationContext
|
||||||
import com.fasterxml.jackson.databind.JsonNode
|
import com.fasterxml.jackson.databind.JsonNode
|
||||||
import com.fasterxml.jackson.databind.deser.std.StdDeserializer
|
import com.fasterxml.jackson.databind.deser.std.StdDeserializer
|
||||||
|
import com.vitorpamplona.quartz.nip47WalletConnect.CancelHoldInvoiceMethod
|
||||||
|
import com.vitorpamplona.quartz.nip47WalletConnect.GetBalanceMethod
|
||||||
|
import com.vitorpamplona.quartz.nip47WalletConnect.GetInfoMethod
|
||||||
|
import com.vitorpamplona.quartz.nip47WalletConnect.ListTransactionsMethod
|
||||||
|
import com.vitorpamplona.quartz.nip47WalletConnect.LookupInvoiceMethod
|
||||||
|
import com.vitorpamplona.quartz.nip47WalletConnect.MakeHoldInvoiceMethod
|
||||||
|
import com.vitorpamplona.quartz.nip47WalletConnect.MakeInvoiceMethod
|
||||||
|
import com.vitorpamplona.quartz.nip47WalletConnect.NwcMethod
|
||||||
import com.vitorpamplona.quartz.nip47WalletConnect.PayInvoiceMethod
|
import com.vitorpamplona.quartz.nip47WalletConnect.PayInvoiceMethod
|
||||||
|
import com.vitorpamplona.quartz.nip47WalletConnect.PayKeysendMethod
|
||||||
import com.vitorpamplona.quartz.nip47WalletConnect.Request
|
import com.vitorpamplona.quartz.nip47WalletConnect.Request
|
||||||
|
import com.vitorpamplona.quartz.nip47WalletConnect.SettleHoldInvoiceMethod
|
||||||
import com.vitorpamplona.quartz.utils.asTextOrNull
|
import com.vitorpamplona.quartz.utils.asTextOrNull
|
||||||
|
|
||||||
class RequestDeserializer : StdDeserializer<Request>(Request::class.java) {
|
class RequestDeserializer : StdDeserializer<Request>(Request::class.java) {
|
||||||
@@ -36,9 +46,18 @@ class RequestDeserializer : StdDeserializer<Request>(Request::class.java) {
|
|||||||
val jsonObject: JsonNode = jp.codec.readTree(jp)
|
val jsonObject: JsonNode = jp.codec.readTree(jp)
|
||||||
val method = jsonObject.get("method")?.asTextOrNull()
|
val method = jsonObject.get("method")?.asTextOrNull()
|
||||||
|
|
||||||
if (method == "pay_invoice") {
|
return when (method) {
|
||||||
return jp.codec.treeToValue(jsonObject, PayInvoiceMethod::class.java)
|
NwcMethod.PAY_INVOICE -> jp.codec.treeToValue(jsonObject, PayInvoiceMethod::class.java)
|
||||||
|
NwcMethod.PAY_KEYSEND -> jp.codec.treeToValue(jsonObject, PayKeysendMethod::class.java)
|
||||||
|
NwcMethod.MAKE_INVOICE -> jp.codec.treeToValue(jsonObject, MakeInvoiceMethod::class.java)
|
||||||
|
NwcMethod.LOOKUP_INVOICE -> jp.codec.treeToValue(jsonObject, LookupInvoiceMethod::class.java)
|
||||||
|
NwcMethod.LIST_TRANSACTIONS -> jp.codec.treeToValue(jsonObject, ListTransactionsMethod::class.java)
|
||||||
|
NwcMethod.GET_BALANCE -> jp.codec.treeToValue(jsonObject, GetBalanceMethod::class.java)
|
||||||
|
NwcMethod.GET_INFO -> jp.codec.treeToValue(jsonObject, GetInfoMethod::class.java)
|
||||||
|
NwcMethod.MAKE_HOLD_INVOICE -> jp.codec.treeToValue(jsonObject, MakeHoldInvoiceMethod::class.java)
|
||||||
|
NwcMethod.CANCEL_HOLD_INVOICE -> jp.codec.treeToValue(jsonObject, CancelHoldInvoiceMethod::class.java)
|
||||||
|
NwcMethod.SETTLE_HOLD_INVOICE -> jp.codec.treeToValue(jsonObject, SettleHoldInvoiceMethod::class.java)
|
||||||
|
else -> null
|
||||||
}
|
}
|
||||||
return null
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
+44
-16
@@ -24,9 +24,21 @@ import com.fasterxml.jackson.core.JsonParser
|
|||||||
import com.fasterxml.jackson.databind.DeserializationContext
|
import com.fasterxml.jackson.databind.DeserializationContext
|
||||||
import com.fasterxml.jackson.databind.JsonNode
|
import com.fasterxml.jackson.databind.JsonNode
|
||||||
import com.fasterxml.jackson.databind.deser.std.StdDeserializer
|
import com.fasterxml.jackson.databind.deser.std.StdDeserializer
|
||||||
|
import com.vitorpamplona.quartz.nip47WalletConnect.CancelHoldInvoiceSuccessResponse
|
||||||
|
import com.vitorpamplona.quartz.nip47WalletConnect.GetBalanceSuccessResponse
|
||||||
|
import com.vitorpamplona.quartz.nip47WalletConnect.GetInfoSuccessResponse
|
||||||
|
import com.vitorpamplona.quartz.nip47WalletConnect.ListTransactionsSuccessResponse
|
||||||
|
import com.vitorpamplona.quartz.nip47WalletConnect.LookupInvoiceSuccessResponse
|
||||||
|
import com.vitorpamplona.quartz.nip47WalletConnect.MakeHoldInvoiceSuccessResponse
|
||||||
|
import com.vitorpamplona.quartz.nip47WalletConnect.MakeInvoiceSuccessResponse
|
||||||
|
import com.vitorpamplona.quartz.nip47WalletConnect.NwcError
|
||||||
|
import com.vitorpamplona.quartz.nip47WalletConnect.NwcErrorResponse
|
||||||
|
import com.vitorpamplona.quartz.nip47WalletConnect.NwcMethod
|
||||||
import com.vitorpamplona.quartz.nip47WalletConnect.PayInvoiceErrorResponse
|
import com.vitorpamplona.quartz.nip47WalletConnect.PayInvoiceErrorResponse
|
||||||
import com.vitorpamplona.quartz.nip47WalletConnect.PayInvoiceSuccessResponse
|
import com.vitorpamplona.quartz.nip47WalletConnect.PayInvoiceSuccessResponse
|
||||||
|
import com.vitorpamplona.quartz.nip47WalletConnect.PayKeysendSuccessResponse
|
||||||
import com.vitorpamplona.quartz.nip47WalletConnect.Response
|
import com.vitorpamplona.quartz.nip47WalletConnect.Response
|
||||||
|
import com.vitorpamplona.quartz.nip47WalletConnect.SettleHoldInvoiceSuccessResponse
|
||||||
import com.vitorpamplona.quartz.utils.asTextOrNull
|
import com.vitorpamplona.quartz.utils.asTextOrNull
|
||||||
|
|
||||||
class ResponseDeserializer : StdDeserializer<Response>(Response::class.java) {
|
class ResponseDeserializer : StdDeserializer<Response>(Response::class.java) {
|
||||||
@@ -36,25 +48,41 @@ class ResponseDeserializer : StdDeserializer<Response>(Response::class.java) {
|
|||||||
): Response? {
|
): Response? {
|
||||||
val jsonObject: JsonNode = jp.codec.readTree(jp)
|
val jsonObject: JsonNode = jp.codec.readTree(jp)
|
||||||
val resultType = jsonObject.get("result_type")?.asTextOrNull()
|
val resultType = jsonObject.get("result_type")?.asTextOrNull()
|
||||||
|
val hasError = jsonObject.has("error") && !jsonObject.get("error").isNull
|
||||||
|
val hasResult = jsonObject.has("result") && !jsonObject.get("result").isNull
|
||||||
|
|
||||||
if (resultType == "pay_invoice") {
|
if (hasError) {
|
||||||
val result = jsonObject.get("result")
|
return when (resultType) {
|
||||||
val error = jsonObject.get("error")
|
NwcMethod.PAY_INVOICE -> jp.codec.treeToValue(jsonObject, PayInvoiceErrorResponse::class.java)
|
||||||
if (result != null) {
|
else -> {
|
||||||
return jp.codec.treeToValue(jsonObject, PayInvoiceSuccessResponse::class.java)
|
val error = jp.codec.treeToValue(jsonObject.get("error"), NwcError::class.java)
|
||||||
}
|
NwcErrorResponse(resultType ?: "", error)
|
||||||
if (error != null) {
|
}
|
||||||
return jp.codec.treeToValue(jsonObject, PayInvoiceErrorResponse::class.java)
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
// tries to guess
|
|
||||||
if (jsonObject.get("result")?.get("preimage") != null) {
|
|
||||||
return jp.codec.treeToValue(jsonObject, PayInvoiceSuccessResponse::class.java)
|
|
||||||
}
|
|
||||||
if (jsonObject.get("error")?.get("code") != null) {
|
|
||||||
return jp.codec.treeToValue(jsonObject, PayInvoiceErrorResponse::class.java)
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (hasResult || resultType != null) {
|
||||||
|
return when (resultType) {
|
||||||
|
NwcMethod.PAY_INVOICE -> jp.codec.treeToValue(jsonObject, PayInvoiceSuccessResponse::class.java)
|
||||||
|
NwcMethod.PAY_KEYSEND -> jp.codec.treeToValue(jsonObject, PayKeysendSuccessResponse::class.java)
|
||||||
|
NwcMethod.MAKE_INVOICE -> jp.codec.treeToValue(jsonObject, MakeInvoiceSuccessResponse::class.java)
|
||||||
|
NwcMethod.LOOKUP_INVOICE -> jp.codec.treeToValue(jsonObject, LookupInvoiceSuccessResponse::class.java)
|
||||||
|
NwcMethod.LIST_TRANSACTIONS -> jp.codec.treeToValue(jsonObject, ListTransactionsSuccessResponse::class.java)
|
||||||
|
NwcMethod.GET_BALANCE -> jp.codec.treeToValue(jsonObject, GetBalanceSuccessResponse::class.java)
|
||||||
|
NwcMethod.GET_INFO -> jp.codec.treeToValue(jsonObject, GetInfoSuccessResponse::class.java)
|
||||||
|
NwcMethod.MAKE_HOLD_INVOICE -> jp.codec.treeToValue(jsonObject, MakeHoldInvoiceSuccessResponse::class.java)
|
||||||
|
NwcMethod.CANCEL_HOLD_INVOICE -> jp.codec.treeToValue(jsonObject, CancelHoldInvoiceSuccessResponse::class.java)
|
||||||
|
NwcMethod.SETTLE_HOLD_INVOICE -> jp.codec.treeToValue(jsonObject, SettleHoldInvoiceSuccessResponse::class.java)
|
||||||
|
else -> {
|
||||||
|
// tries to guess for backward compatibility
|
||||||
|
if (jsonObject.get("result")?.get("preimage") != null) {
|
||||||
|
return jp.codec.treeToValue(jsonObject, PayInvoiceSuccessResponse::class.java)
|
||||||
|
}
|
||||||
|
null
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
return null
|
return null
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user