feat: add high-level Nip47Client and Nip47Server APIs for NIP-47
Simplifies the NWC developer experience by providing Nip47Client (for wallet client apps) and Nip47Server (for wallet service backends) that handle URI parsing, signer creation, event building, filter construction, and response decryption. Updates README with quick-start examples. https://claude.ai/code/session_01JFogiwyR4CPVP8cRznqahJ
This commit is contained in:
+265
@@ -0,0 +1,265 @@
|
||||
/*
|
||||
* 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.HexKey
|
||||
import com.vitorpamplona.quartz.nip01Core.core.hexToByteArray
|
||||
import com.vitorpamplona.quartz.nip01Core.crypto.KeyPair
|
||||
import com.vitorpamplona.quartz.nip01Core.relay.filters.Filter
|
||||
import com.vitorpamplona.quartz.nip01Core.relay.normalizer.NormalizedRelayUrl
|
||||
import com.vitorpamplona.quartz.nip01Core.signers.NostrSigner
|
||||
import com.vitorpamplona.quartz.nip01Core.signers.NostrSignerInternal
|
||||
|
||||
/**
|
||||
* High-level NIP-47 Wallet Connect client.
|
||||
*
|
||||
* Simplifies the NWC protocol by handling URI parsing, signer creation,
|
||||
* event building, filter construction, and response decryption.
|
||||
*
|
||||
* Usage:
|
||||
* ```kotlin
|
||||
* val client = Nip47Client.fromUri("nostr+walletconnect://pubkey?relay=...&secret=...")
|
||||
*
|
||||
* // Build a request event
|
||||
* val requestEvent = client.payInvoice("lnbc50n1...")
|
||||
*
|
||||
* // Send requestEvent to client.relayUrl via your relay connection
|
||||
* // Subscribe using client.responseFilter(requestEvent.id) for the response
|
||||
*
|
||||
* // When response arrives:
|
||||
* val response = client.parseResponse(responseEvent)
|
||||
* when (response) {
|
||||
* is PayInvoiceSuccessResponse -> println("Paid! Preimage: ${response.result?.preimage}")
|
||||
* is NwcErrorResponse -> println("Error: ${response.error?.message}")
|
||||
* }
|
||||
* ```
|
||||
*/
|
||||
class Nip47Client(
|
||||
val walletPubKeyHex: HexKey,
|
||||
val relayUrl: NormalizedRelayUrl,
|
||||
val signer: NostrSigner,
|
||||
val useNip44: Boolean = false,
|
||||
) {
|
||||
companion object {
|
||||
/**
|
||||
* Creates an Nip47Client from a NWC connection URI string.
|
||||
*
|
||||
* @param uri NWC URI (e.g., "nostr+walletconnect://pubkey?relay=...&secret=...")
|
||||
* @throws IllegalArgumentException if the URI is invalid or has no secret
|
||||
*/
|
||||
fun fromUri(uri: String): Nip47Client {
|
||||
val config = Nip47WalletConnect.parse(uri)
|
||||
return fromNip47URI(config)
|
||||
}
|
||||
|
||||
/**
|
||||
* Creates an Nip47Client from parsed NWC connection details.
|
||||
*
|
||||
* @param config parsed NWC URI with wallet pubkey, relay, and secret
|
||||
* @throws IllegalArgumentException if config has no secret
|
||||
*/
|
||||
fun fromNip47URI(config: Nip47WalletConnect.Nip47URINorm): Nip47Client {
|
||||
val secret = config.secret ?: throw IllegalArgumentException("NWC connection requires a secret")
|
||||
val signer = NostrSignerInternal(KeyPair(secret.hexToByteArray()))
|
||||
return Nip47Client(
|
||||
walletPubKeyHex = config.pubKeyHex,
|
||||
relayUrl = config.relayUri,
|
||||
signer = signer,
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
// --- Request builders ---
|
||||
|
||||
/**
|
||||
* Builds a pay_invoice request event.
|
||||
*/
|
||||
suspend fun payInvoice(
|
||||
bolt11: String,
|
||||
amount: Long? = null,
|
||||
): LnZapPaymentRequestEvent =
|
||||
buildRequest(
|
||||
if (amount != null) {
|
||||
PayInvoiceMethod.create(bolt11, amount)
|
||||
} else {
|
||||
PayInvoiceMethod.create(bolt11)
|
||||
},
|
||||
)
|
||||
|
||||
/**
|
||||
* Builds a pay_keysend request event.
|
||||
*/
|
||||
suspend fun payKeysend(
|
||||
amount: Long,
|
||||
pubkey: String,
|
||||
preimage: String? = null,
|
||||
tlvRecords: List<TlvRecord>? = null,
|
||||
): LnZapPaymentRequestEvent = buildRequest(PayKeysendMethod.create(amount, pubkey, preimage, tlvRecords))
|
||||
|
||||
/**
|
||||
* Builds a get_balance request event.
|
||||
*/
|
||||
suspend fun getBalance(): LnZapPaymentRequestEvent = buildRequest(GetBalanceMethod.create())
|
||||
|
||||
/**
|
||||
* Builds a get_info request event.
|
||||
*/
|
||||
suspend fun getInfo(): LnZapPaymentRequestEvent = buildRequest(GetInfoMethod.create())
|
||||
|
||||
/**
|
||||
* Builds a make_invoice request event.
|
||||
*/
|
||||
suspend fun makeInvoice(
|
||||
amount: Long,
|
||||
description: String? = null,
|
||||
descriptionHash: String? = null,
|
||||
expiry: Long? = null,
|
||||
): LnZapPaymentRequestEvent = buildRequest(MakeInvoiceMethod.create(amount, description, descriptionHash, expiry))
|
||||
|
||||
/**
|
||||
* Builds a lookup_invoice request event by payment hash.
|
||||
*/
|
||||
suspend fun lookupInvoiceByHash(paymentHash: String): LnZapPaymentRequestEvent =
|
||||
buildRequest(LookupInvoiceMethod.createByHash(paymentHash))
|
||||
|
||||
/**
|
||||
* Builds a lookup_invoice request event by BOLT11 invoice.
|
||||
*/
|
||||
suspend fun lookupInvoiceByInvoice(invoice: String): LnZapPaymentRequestEvent =
|
||||
buildRequest(LookupInvoiceMethod.createByInvoice(invoice))
|
||||
|
||||
/**
|
||||
* Builds a list_transactions request event.
|
||||
*/
|
||||
suspend fun listTransactions(
|
||||
from: Long? = null,
|
||||
until: Long? = null,
|
||||
limit: Int? = null,
|
||||
offset: Int? = null,
|
||||
unpaid: Boolean? = null,
|
||||
type: String? = null,
|
||||
): LnZapPaymentRequestEvent = buildRequest(ListTransactionsMethod.create(from, until, limit, offset, unpaid, type))
|
||||
|
||||
/**
|
||||
* Builds a get_budget request event.
|
||||
*/
|
||||
suspend fun getBudget(): LnZapPaymentRequestEvent = buildRequest(GetBudgetMethod.create())
|
||||
|
||||
/**
|
||||
* Builds a sign_message request event.
|
||||
*/
|
||||
suspend fun signMessage(message: String): LnZapPaymentRequestEvent = buildRequest(SignMessageMethod.create(message))
|
||||
|
||||
/**
|
||||
* Builds a make_hold_invoice request event.
|
||||
*/
|
||||
suspend fun makeHoldInvoice(
|
||||
amount: Long,
|
||||
paymentHash: String,
|
||||
description: String? = null,
|
||||
descriptionHash: String? = null,
|
||||
expiry: Long? = null,
|
||||
minCltvExpiryDelta: Int? = null,
|
||||
): LnZapPaymentRequestEvent =
|
||||
buildRequest(MakeHoldInvoiceMethod.create(amount, paymentHash, description, descriptionHash, expiry, minCltvExpiryDelta))
|
||||
|
||||
/**
|
||||
* Builds a cancel_hold_invoice request event.
|
||||
*/
|
||||
suspend fun cancelHoldInvoice(paymentHash: String): LnZapPaymentRequestEvent =
|
||||
buildRequest(CancelHoldInvoiceMethod.create(paymentHash))
|
||||
|
||||
/**
|
||||
* Builds a settle_hold_invoice request event.
|
||||
*/
|
||||
suspend fun settleHoldInvoice(preimage: String): LnZapPaymentRequestEvent =
|
||||
buildRequest(SettleHoldInvoiceMethod.create(preimage))
|
||||
|
||||
/**
|
||||
* Builds a request event from any [Request] object.
|
||||
* This is the low-level method used by all convenience methods above.
|
||||
*/
|
||||
suspend fun buildRequest(request: Request): LnZapPaymentRequestEvent =
|
||||
LnZapPaymentRequestEvent.createRequest(
|
||||
request = request,
|
||||
walletServicePubkey = walletPubKeyHex,
|
||||
signer = signer,
|
||||
useNip44 = useNip44,
|
||||
)
|
||||
|
||||
// --- Response handling ---
|
||||
|
||||
/**
|
||||
* Decrypts and parses a response event from the wallet.
|
||||
*/
|
||||
suspend fun parseResponse(event: LnZapPaymentResponseEvent): Response = event.decrypt(signer)
|
||||
|
||||
/**
|
||||
* Decrypts and parses a notification event from the wallet.
|
||||
*/
|
||||
suspend fun parseNotification(event: NwcNotificationEvent): Notification = event.decryptNotification(signer)
|
||||
|
||||
// --- Filter helpers ---
|
||||
|
||||
/**
|
||||
* Creates a filter to subscribe for responses to a specific request.
|
||||
* Use this to subscribe on [relayUrl] after sending a request event.
|
||||
*/
|
||||
fun responseFilter(requestEventId: HexKey): Filter =
|
||||
Filter(
|
||||
kinds = listOf(LnZapPaymentResponseEvent.KIND),
|
||||
authors = listOf(walletPubKeyHex),
|
||||
tags = mapOf("e" to listOf(requestEventId)),
|
||||
)
|
||||
|
||||
/**
|
||||
* Creates a filter to subscribe for all responses from the wallet
|
||||
* directed to this client.
|
||||
*/
|
||||
fun allResponsesFilter(since: Long? = null): Filter =
|
||||
Filter(
|
||||
kinds = listOf(LnZapPaymentResponseEvent.KIND),
|
||||
authors = listOf(walletPubKeyHex),
|
||||
tags = mapOf("p" to listOf(signer.pubKey)),
|
||||
since = since,
|
||||
)
|
||||
|
||||
/**
|
||||
* Creates a filter to subscribe for wallet notifications.
|
||||
*/
|
||||
fun notificationsFilter(since: Long? = null): Filter =
|
||||
Filter(
|
||||
kinds = listOf(NwcNotificationEvent.KIND, NwcNotificationEvent.LEGACY_KIND),
|
||||
authors = listOf(walletPubKeyHex),
|
||||
tags = mapOf("p" to listOf(signer.pubKey)),
|
||||
since = since,
|
||||
)
|
||||
|
||||
/**
|
||||
* Creates a filter to fetch the wallet's info event (kind 13194).
|
||||
*/
|
||||
fun infoFilter(): Filter =
|
||||
Filter(
|
||||
kinds = listOf(NwcInfoEvent.KIND),
|
||||
authors = listOf(walletPubKeyHex),
|
||||
limit = 1,
|
||||
)
|
||||
}
|
||||
+294
@@ -0,0 +1,294 @@
|
||||
/*
|
||||
* 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.HexKey
|
||||
import com.vitorpamplona.quartz.nip01Core.relay.filters.Filter
|
||||
import com.vitorpamplona.quartz.nip01Core.signers.NostrSigner
|
||||
|
||||
/**
|
||||
* High-level NIP-47 Wallet Connect server (wallet service).
|
||||
*
|
||||
* Simplifies building a wallet service that receives NWC requests from clients,
|
||||
* processes them, and sends back responses and notifications.
|
||||
*
|
||||
* Usage:
|
||||
* ```kotlin
|
||||
* val server = Nip47Server(walletSigner, supportedMethods, relayUrl)
|
||||
*
|
||||
* // Publish capabilities
|
||||
* val infoEvent = server.buildInfoEvent()
|
||||
* // Send infoEvent to relay
|
||||
*
|
||||
* // Subscribe using server.requestsFilter() on your relay
|
||||
*
|
||||
* // When a request arrives:
|
||||
* val request = server.parseRequest(requestEvent)
|
||||
* when (request) {
|
||||
* is GetBalanceMethod -> {
|
||||
* val response = server.respondGetBalance(requestEvent, balance = 2100000L)
|
||||
* // Send response to relay
|
||||
* }
|
||||
* is PayInvoiceMethod -> {
|
||||
* // Process payment, then:
|
||||
* val response = server.respondPayInvoice(requestEvent, preimage = "abc123")
|
||||
* // Or on error:
|
||||
* val error = server.respondError(requestEvent, NwcErrorCode.PAYMENT_FAILED, "Route not found")
|
||||
* // Send response to relay
|
||||
* }
|
||||
* }
|
||||
* ```
|
||||
*/
|
||||
class Nip47Server(
|
||||
val signer: NostrSigner,
|
||||
val capabilities: List<String> = emptyList(),
|
||||
val useNip44: Boolean = false,
|
||||
val encryptionSchemes: List<String>? = null,
|
||||
val notificationTypes: List<String>? = null,
|
||||
) {
|
||||
// --- Info event ---
|
||||
|
||||
/**
|
||||
* Builds a kind 13194 info event advertising wallet capabilities.
|
||||
* Sign and publish this event to your relay.
|
||||
*/
|
||||
fun buildInfoEvent() =
|
||||
NwcInfoEvent.build(
|
||||
capabilities = capabilities,
|
||||
encryptionSchemes = encryptionSchemes,
|
||||
notificationTypes = notificationTypes,
|
||||
)
|
||||
|
||||
// --- Request parsing ---
|
||||
|
||||
/**
|
||||
* Decrypts and parses an incoming client request.
|
||||
*/
|
||||
suspend fun parseRequest(event: LnZapPaymentRequestEvent): Request = event.decryptRequest(signer)
|
||||
|
||||
// --- Response builders ---
|
||||
|
||||
/**
|
||||
* Builds a response event from any [Response] object.
|
||||
*/
|
||||
suspend fun buildResponse(
|
||||
response: Response,
|
||||
requestEvent: LnZapPaymentRequestEvent,
|
||||
): LnZapPaymentResponseEvent =
|
||||
LnZapPaymentResponseEvent.createResponse(
|
||||
response = response,
|
||||
requestEvent = requestEvent,
|
||||
signer = signer,
|
||||
useNip44 = useNip44,
|
||||
)
|
||||
|
||||
/**
|
||||
* Builds an error response for any method.
|
||||
*/
|
||||
suspend fun respondError(
|
||||
requestEvent: LnZapPaymentRequestEvent,
|
||||
code: NwcErrorCode,
|
||||
message: String,
|
||||
resultType: String? = null,
|
||||
): LnZapPaymentResponseEvent {
|
||||
val method = resultType ?: requestEvent.decryptRequest(signer).method ?: NwcMethod.PAY_INVOICE
|
||||
return buildResponse(NwcErrorResponse(method, NwcError(code, message)), requestEvent)
|
||||
}
|
||||
|
||||
/**
|
||||
* Builds a pay_invoice success response.
|
||||
*/
|
||||
suspend fun respondPayInvoice(
|
||||
requestEvent: LnZapPaymentRequestEvent,
|
||||
preimage: String? = null,
|
||||
feesPaid: Long? = null,
|
||||
): LnZapPaymentResponseEvent =
|
||||
buildResponse(
|
||||
PayInvoiceSuccessResponse(PayInvoiceSuccessResponse.PayInvoiceResultParams(preimage, feesPaid)),
|
||||
requestEvent,
|
||||
)
|
||||
|
||||
/**
|
||||
* Builds a pay_keysend success response.
|
||||
*/
|
||||
suspend fun respondPayKeysend(
|
||||
requestEvent: LnZapPaymentRequestEvent,
|
||||
preimage: String? = null,
|
||||
feesPaid: Long? = null,
|
||||
): LnZapPaymentResponseEvent =
|
||||
buildResponse(
|
||||
PayKeysendSuccessResponse(PayKeysendSuccessResponse.PayKeysendResult(preimage, feesPaid)),
|
||||
requestEvent,
|
||||
)
|
||||
|
||||
/**
|
||||
* Builds a get_balance success response.
|
||||
*/
|
||||
suspend fun respondGetBalance(
|
||||
requestEvent: LnZapPaymentRequestEvent,
|
||||
balance: Long,
|
||||
): LnZapPaymentResponseEvent =
|
||||
buildResponse(
|
||||
GetBalanceSuccessResponse(GetBalanceSuccessResponse.GetBalanceResult(balance)),
|
||||
requestEvent,
|
||||
)
|
||||
|
||||
/**
|
||||
* Builds a get_info success response.
|
||||
*/
|
||||
suspend fun respondGetInfo(
|
||||
requestEvent: LnZapPaymentRequestEvent,
|
||||
alias: String? = null,
|
||||
color: String? = null,
|
||||
pubkey: String? = null,
|
||||
network: String? = null,
|
||||
blockHeight: Long? = null,
|
||||
blockHash: String? = null,
|
||||
methods: List<String>? = null,
|
||||
notifications: List<String>? = null,
|
||||
lud16: String? = null,
|
||||
): LnZapPaymentResponseEvent =
|
||||
buildResponse(
|
||||
GetInfoSuccessResponse(
|
||||
GetInfoSuccessResponse.GetInfoResult(
|
||||
alias, color, pubkey, network, blockHeight, blockHash, methods, notifications, null, lud16,
|
||||
),
|
||||
),
|
||||
requestEvent,
|
||||
)
|
||||
|
||||
/**
|
||||
* Builds a make_invoice success response.
|
||||
*/
|
||||
suspend fun respondMakeInvoice(
|
||||
requestEvent: LnZapPaymentRequestEvent,
|
||||
transaction: NwcTransaction,
|
||||
): LnZapPaymentResponseEvent =
|
||||
buildResponse(MakeInvoiceSuccessResponse(transaction), requestEvent)
|
||||
|
||||
/**
|
||||
* Builds a lookup_invoice success response.
|
||||
*/
|
||||
suspend fun respondLookupInvoice(
|
||||
requestEvent: LnZapPaymentRequestEvent,
|
||||
transaction: NwcTransaction,
|
||||
): LnZapPaymentResponseEvent =
|
||||
buildResponse(LookupInvoiceSuccessResponse(transaction), requestEvent)
|
||||
|
||||
/**
|
||||
* Builds a list_transactions success response.
|
||||
*/
|
||||
suspend fun respondListTransactions(
|
||||
requestEvent: LnZapPaymentRequestEvent,
|
||||
transactions: List<NwcTransaction>,
|
||||
totalCount: Long? = null,
|
||||
): LnZapPaymentResponseEvent =
|
||||
buildResponse(
|
||||
ListTransactionsSuccessResponse(
|
||||
ListTransactionsSuccessResponse.ListTransactionsResult(transactions, totalCount),
|
||||
),
|
||||
requestEvent,
|
||||
)
|
||||
|
||||
/**
|
||||
* Builds a get_budget success response.
|
||||
*/
|
||||
suspend fun respondGetBudget(
|
||||
requestEvent: LnZapPaymentRequestEvent,
|
||||
usedBudget: Long? = null,
|
||||
totalBudget: Long? = null,
|
||||
renewsAt: Long? = null,
|
||||
renewalPeriod: String? = null,
|
||||
): LnZapPaymentResponseEvent =
|
||||
buildResponse(
|
||||
GetBudgetSuccessResponse(
|
||||
GetBudgetSuccessResponse.GetBudgetResult(usedBudget, totalBudget, renewsAt, renewalPeriod),
|
||||
),
|
||||
requestEvent,
|
||||
)
|
||||
|
||||
/**
|
||||
* Builds a sign_message success response.
|
||||
*/
|
||||
suspend fun respondSignMessage(
|
||||
requestEvent: LnZapPaymentRequestEvent,
|
||||
message: String,
|
||||
signature: String,
|
||||
): LnZapPaymentResponseEvent =
|
||||
buildResponse(
|
||||
SignMessageSuccessResponse(SignMessageSuccessResponse.SignMessageResult(message, signature)),
|
||||
requestEvent,
|
||||
)
|
||||
|
||||
// --- Notification builders ---
|
||||
|
||||
/**
|
||||
* Builds a payment_received notification event.
|
||||
*/
|
||||
suspend fun notifyPaymentReceived(
|
||||
clientPubkey: HexKey,
|
||||
transaction: NwcTransaction,
|
||||
): NwcNotificationEvent =
|
||||
NwcNotificationEvent.createNotification(
|
||||
notification = PaymentReceivedNotification(transaction),
|
||||
clientPubkey = clientPubkey,
|
||||
signer = signer,
|
||||
)
|
||||
|
||||
/**
|
||||
* Builds a payment_sent notification event.
|
||||
*/
|
||||
suspend fun notifyPaymentSent(
|
||||
clientPubkey: HexKey,
|
||||
transaction: NwcTransaction,
|
||||
): NwcNotificationEvent =
|
||||
NwcNotificationEvent.createNotification(
|
||||
notification = PaymentSentNotification(transaction),
|
||||
clientPubkey = clientPubkey,
|
||||
signer = signer,
|
||||
)
|
||||
|
||||
/**
|
||||
* Builds a notification event from any [Notification] object.
|
||||
*/
|
||||
suspend fun buildNotification(
|
||||
notification: Notification,
|
||||
clientPubkey: HexKey,
|
||||
): NwcNotificationEvent =
|
||||
NwcNotificationEvent.createNotification(
|
||||
notification = notification,
|
||||
clientPubkey = clientPubkey,
|
||||
signer = signer,
|
||||
)
|
||||
|
||||
// --- Filter helpers ---
|
||||
|
||||
/**
|
||||
* Creates a filter to subscribe for incoming client requests.
|
||||
* Use this to subscribe on your relay.
|
||||
*/
|
||||
fun requestsFilter(since: Long? = null): Filter =
|
||||
Filter(
|
||||
kinds = listOf(LnZapPaymentRequestEvent.KIND),
|
||||
tags = mapOf("p" to listOf(signer.pubKey)),
|
||||
since = since,
|
||||
)
|
||||
}
|
||||
+140
-258
@@ -4,10 +4,86 @@ Quartz implementation of [NIP-47](https://github.com/nostr-protocol/nips/blob/ma
|
||||
Wallet Connect (NWC). This module provides everything needed to build both **wallet client apps**
|
||||
(like Amethyst) and **wallet service backends** (like Alby Hub).
|
||||
|
||||
## Quick Start — Wallet Client
|
||||
|
||||
Use `Nip47Client` for a high-level API that handles URI parsing, signer creation,
|
||||
event building, filter construction, and response decryption:
|
||||
|
||||
```kotlin
|
||||
// 1. Create client from NWC URI
|
||||
val client = Nip47Client.fromUri("nostr+walletconnect://pubkey?relay=...&secret=...")
|
||||
|
||||
// 2. Build request events — one method per NWC command
|
||||
val payEvent = client.payInvoice("lnbc50n1...")
|
||||
val balanceEvent = client.getBalance()
|
||||
val infoEvent = client.getInfo()
|
||||
val invoiceEvent = client.makeInvoice(amount = 50000L, description = "Coffee")
|
||||
val txEvent = client.listTransactions(limit = 20)
|
||||
|
||||
// 3. Send event to client.relayUrl via your relay connection
|
||||
// 4. Subscribe using client.responseFilter(payEvent.id) for the response
|
||||
|
||||
// 5. When response arrives, parse it
|
||||
val response = client.parseResponse(responseEvent)
|
||||
when (response) {
|
||||
is PayInvoiceSuccessResponse -> println("Paid! Preimage: ${response.result?.preimage}")
|
||||
is GetBalanceSuccessResponse -> println("Balance: ${response.result?.balance} msats")
|
||||
is NwcErrorResponse -> println("Error: ${response.error?.message}")
|
||||
}
|
||||
|
||||
// Filter helpers for relay subscriptions
|
||||
val filter = client.responseFilter(payEvent.id) // Filter for a specific response
|
||||
val allFilter = client.allResponsesFilter() // Filter for all responses
|
||||
val notifFilter = client.notificationsFilter() // Filter for notifications
|
||||
val walletInfo = client.infoFilter() // Filter for wallet info event
|
||||
```
|
||||
|
||||
## Quick Start — Wallet Service
|
||||
|
||||
Use `Nip47Server` to build a wallet service that receives requests and sends responses:
|
||||
|
||||
```kotlin
|
||||
// 1. Create server
|
||||
val server = Nip47Server(
|
||||
signer = walletSigner,
|
||||
capabilities = listOf(NwcMethod.PAY_INVOICE, NwcMethod.GET_BALANCE, NwcMethod.GET_INFO),
|
||||
)
|
||||
|
||||
// 2. Publish capabilities (kind 13194)
|
||||
val infoTemplate = server.buildInfoEvent()
|
||||
// Sign and send: walletSigner.sign(infoTemplate)
|
||||
|
||||
// 3. Subscribe using server.requestsFilter() on your relay
|
||||
|
||||
// 4. When a request arrives, parse and respond
|
||||
val request = server.parseRequest(requestEvent)
|
||||
when (request) {
|
||||
is GetBalanceMethod -> {
|
||||
val response = server.respondGetBalance(requestEvent, balance = 2100000L)
|
||||
// Send response to relay
|
||||
}
|
||||
is PayInvoiceMethod -> {
|
||||
// Process payment, then:
|
||||
val response = server.respondPayInvoice(requestEvent, preimage = "abc123")
|
||||
// Or on error:
|
||||
val error = server.respondError(requestEvent, NwcErrorCode.PAYMENT_FAILED, "Route not found")
|
||||
}
|
||||
is MakeInvoiceMethod -> {
|
||||
val tx = NwcTransaction(type = NwcTransactionType.INCOMING, invoice = "lnbc...")
|
||||
val response = server.respondMakeInvoice(requestEvent, tx)
|
||||
}
|
||||
}
|
||||
|
||||
// 5. Send notifications
|
||||
val notifEvent = server.notifyPaymentReceived(clientPubkey, transaction)
|
||||
```
|
||||
|
||||
## Architecture
|
||||
|
||||
```
|
||||
nip47WalletConnect/
|
||||
├── Nip47Client.kt # High-level client API (URI → requests → responses)
|
||||
├── Nip47Server.kt # High-level server API (requests → responses → notifications)
|
||||
├── Nip47WalletConnect.kt # URI parsing (nostr+walletconnect://)
|
||||
├── Request.kt # All 13 NWC request methods + params
|
||||
├── Response.kt # All response types (success + error)
|
||||
@@ -38,49 +114,42 @@ nip47WalletConnect/
|
||||
|
||||
## Supported Methods
|
||||
|
||||
| Method | Request Class | Success Response Class |
|
||||
|----------------------|---------------------------|-----------------------------------|
|
||||
| `pay_invoice` | `PayInvoiceMethod` | `PayInvoiceSuccessResponse` |
|
||||
| `pay_keysend` | `PayKeysendMethod` | `PayKeysendSuccessResponse` |
|
||||
| `make_invoice` | `MakeInvoiceMethod` | `MakeInvoiceSuccessResponse` |
|
||||
| `lookup_invoice` | `LookupInvoiceMethod` | `LookupInvoiceSuccessResponse` |
|
||||
| `list_transactions` | `ListTransactionsMethod` | `ListTransactionsSuccessResponse` |
|
||||
| `get_balance` | `GetBalanceMethod` | `GetBalanceSuccessResponse` |
|
||||
| `get_info` | `GetInfoMethod` | `GetInfoSuccessResponse` |
|
||||
| `get_budget` | `GetBudgetMethod` | `GetBudgetSuccessResponse` |
|
||||
| `sign_message` | `SignMessageMethod` | `SignMessageSuccessResponse` |
|
||||
| `create_connection` | `CreateConnectionMethod` | `CreateConnectionSuccessResponse` |
|
||||
| `make_hold_invoice` | `MakeHoldInvoiceMethod` | `MakeHoldInvoiceSuccessResponse` |
|
||||
| `cancel_hold_invoice`| `CancelHoldInvoiceMethod` | `CancelHoldInvoiceSuccessResponse`|
|
||||
| `settle_hold_invoice`| `SettleHoldInvoiceMethod` | `SettleHoldInvoiceSuccessResponse`|
|
||||
| Method | `Nip47Client` method | Request Class | Success Response Class |
|
||||
|----------------------|-----------------------------|---------------------------|-----------------------------------|
|
||||
| `pay_invoice` | `payInvoice()` | `PayInvoiceMethod` | `PayInvoiceSuccessResponse` |
|
||||
| `pay_keysend` | `payKeysend()` | `PayKeysendMethod` | `PayKeysendSuccessResponse` |
|
||||
| `make_invoice` | `makeInvoice()` | `MakeInvoiceMethod` | `MakeInvoiceSuccessResponse` |
|
||||
| `lookup_invoice` | `lookupInvoiceByHash/ByInvoice()` | `LookupInvoiceMethod`| `LookupInvoiceSuccessResponse` |
|
||||
| `list_transactions` | `listTransactions()` | `ListTransactionsMethod` | `ListTransactionsSuccessResponse` |
|
||||
| `get_balance` | `getBalance()` | `GetBalanceMethod` | `GetBalanceSuccessResponse` |
|
||||
| `get_info` | `getInfo()` | `GetInfoMethod` | `GetInfoSuccessResponse` |
|
||||
| `get_budget` | `getBudget()` | `GetBudgetMethod` | `GetBudgetSuccessResponse` |
|
||||
| `sign_message` | `signMessage()` | `SignMessageMethod` | `SignMessageSuccessResponse` |
|
||||
| `create_connection` | `buildRequest()` | `CreateConnectionMethod` | `CreateConnectionSuccessResponse` |
|
||||
| `make_hold_invoice` | `makeHoldInvoice()` | `MakeHoldInvoiceMethod` | `MakeHoldInvoiceSuccessResponse` |
|
||||
| `cancel_hold_invoice`| `cancelHoldInvoice()` | `CancelHoldInvoiceMethod` | `CancelHoldInvoiceSuccessResponse`|
|
||||
| `settle_hold_invoice`| `settleHoldInvoice()` | `SettleHoldInvoiceMethod` | `SettleHoldInvoiceSuccessResponse`|
|
||||
|
||||
Any method can also return `NwcErrorResponse` or (for `pay_invoice`) `PayInvoiceErrorResponse`.
|
||||
|
||||
## Implementing a Wallet Client
|
||||
## Low-Level API
|
||||
|
||||
A wallet client connects to a user's lightning wallet to send payments, check
|
||||
balances, create invoices, and list transactions.
|
||||
The high-level `Nip47Client` and `Nip47Server` classes wrap the lower-level event
|
||||
builders. You can use these directly if you need more control.
|
||||
|
||||
### 1. Parse the NWC Connection URI
|
||||
### Wallet Client (Low-Level)
|
||||
|
||||
Users provide an NWC connection string from their wallet provider:
|
||||
#### 1. Parse the NWC Connection URI
|
||||
|
||||
```kotlin
|
||||
val uri = "nostr+walletconnect://b889ff5b...?relay=wss%3A%2F%2Frelay.damus.io&secret=71a8c14c..."
|
||||
val nwcConfig = Nip47WalletConnect.parse(uri)
|
||||
|
||||
// nwcConfig.pubKeyHex — wallet service pubkey
|
||||
// nwcConfig.relayUri — relay to communicate through (NormalizedRelayUrl)
|
||||
// nwcConfig.secret — hex secret for the client signer
|
||||
// nwcConfig.lud16 — optional lightning address
|
||||
```
|
||||
|
||||
Supported URI schemes: `nostr+walletconnect://`, `nostrwalletconnect://`,
|
||||
`amethyst+walletconnect://`
|
||||
|
||||
### 2. Create the Client Signer
|
||||
|
||||
The `secret` from the URI becomes the client's signing key:
|
||||
#### 2. Create the Client Signer
|
||||
|
||||
```kotlin
|
||||
val clientSigner = NostrSignerInternal(
|
||||
@@ -88,12 +157,9 @@ val clientSigner = NostrSignerInternal(
|
||||
)
|
||||
```
|
||||
|
||||
### 3. Build and Send Requests
|
||||
|
||||
Use `LnZapPaymentRequestEvent.createRequest()` to create encrypted request events:
|
||||
#### 3. Build and Send Requests
|
||||
|
||||
```kotlin
|
||||
// Get balance
|
||||
val balanceRequest = GetBalanceMethod.create()
|
||||
val event = LnZapPaymentRequestEvent.createRequest(
|
||||
request = balanceRequest,
|
||||
@@ -101,36 +167,6 @@ val event = LnZapPaymentRequestEvent.createRequest(
|
||||
signer = clientSigner,
|
||||
)
|
||||
// Send `event` to `nwcConfig.relayUri`
|
||||
|
||||
// Pay an invoice
|
||||
val payRequest = PayInvoiceMethod.create("lnbc50n1...")
|
||||
val payEvent = LnZapPaymentRequestEvent.createRequest(
|
||||
request = payRequest,
|
||||
walletServicePubkey = nwcConfig.pubKeyHex,
|
||||
signer = clientSigner,
|
||||
)
|
||||
|
||||
// Create an invoice (amount in millisats)
|
||||
val invoiceRequest = MakeInvoiceMethod.create(
|
||||
amount = 50000L, // 50 sats in millisats
|
||||
description = "Coffee",
|
||||
)
|
||||
val invoiceEvent = LnZapPaymentRequestEvent.createRequest(
|
||||
request = invoiceRequest,
|
||||
walletServicePubkey = nwcConfig.pubKeyHex,
|
||||
signer = clientSigner,
|
||||
)
|
||||
|
||||
// List transactions with pagination
|
||||
val listRequest = ListTransactionsMethod.create(
|
||||
limit = 20,
|
||||
offset = 0,
|
||||
)
|
||||
val listEvent = LnZapPaymentRequestEvent.createRequest(
|
||||
request = listRequest,
|
||||
walletServicePubkey = nwcConfig.pubKeyHex,
|
||||
signer = clientSigner,
|
||||
)
|
||||
```
|
||||
|
||||
To use NIP-44 encryption instead of NIP-04:
|
||||
@@ -144,202 +180,69 @@ val event = LnZapPaymentRequestEvent.createRequest(
|
||||
)
|
||||
```
|
||||
|
||||
### 4. Receive and Parse Responses
|
||||
#### 4. Receive and Parse Responses
|
||||
|
||||
Subscribe to kind `23195` events on the NWC relay, filtered by the wallet
|
||||
service pubkey and the request event ID. When a response arrives:
|
||||
service pubkey and the request event ID:
|
||||
|
||||
```kotlin
|
||||
// responseEvent is a LnZapPaymentResponseEvent (kind 23195)
|
||||
val response: Response = responseEvent.decrypt(clientSigner)
|
||||
|
||||
when (response) {
|
||||
is GetBalanceSuccessResponse -> {
|
||||
val balanceMillisats = response.result?.balance ?: 0L
|
||||
val balanceSats = balanceMillisats / 1000L
|
||||
val balanceSats = (response.result?.balance ?: 0L) / 1000L
|
||||
}
|
||||
is PayInvoiceSuccessResponse -> {
|
||||
val preimage = response.result?.preimage
|
||||
val feesPaid = response.result?.fees_paid
|
||||
}
|
||||
is MakeInvoiceSuccessResponse -> {
|
||||
val bolt11 = response.result?.invoice
|
||||
val paymentHash = response.result?.payment_hash
|
||||
}
|
||||
is ListTransactionsSuccessResponse -> {
|
||||
val transactions: List<NwcTransaction> = response.result?.transactions ?: emptyList()
|
||||
transactions.forEach { tx ->
|
||||
// tx.type — "incoming" or "outgoing"
|
||||
// tx.amount — in millisats
|
||||
// tx.description, tx.created_at, tx.state, etc.
|
||||
}
|
||||
}
|
||||
is GetInfoSuccessResponse -> {
|
||||
val alias = response.result?.alias
|
||||
val methods = response.result?.methods // supported methods
|
||||
val lud16 = response.result?.lud16
|
||||
}
|
||||
is PayInvoiceErrorResponse -> {
|
||||
val errorCode = response.error?.code // NwcErrorCode enum
|
||||
val errorMessage = response.error?.message
|
||||
}
|
||||
is NwcErrorResponse -> {
|
||||
val errorCode = response.error?.code
|
||||
val errorMessage = response.error?.message
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
### 5. Listen for Notifications (Optional)
|
||||
|
||||
Subscribe to kind `23196`/`23197` events from the wallet:
|
||||
#### 5. Listen for Notifications
|
||||
|
||||
```kotlin
|
||||
// notificationEvent is an NwcNotificationEvent
|
||||
val notification: Notification = notificationEvent.decryptNotification(clientSigner)
|
||||
|
||||
when (notification) {
|
||||
is PaymentReceivedNotification -> {
|
||||
val tx: NwcTransaction? = notification.notification
|
||||
// tx?.amount, tx?.description, tx?.payment_hash, etc.
|
||||
}
|
||||
is PaymentSentNotification -> {
|
||||
val tx: NwcTransaction? = notification.notification
|
||||
}
|
||||
is HoldInvoiceAcceptedNotification -> {
|
||||
val data = notification.notification
|
||||
// data?.payment_hash, data?.amount, data?.settle_deadline
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
### 6. Transaction State Helpers
|
||||
### Wallet Service (Low-Level)
|
||||
|
||||
Transaction states from different wallet implementations may use different
|
||||
casing. Use the case-insensitive helpers:
|
||||
#### 1. Publish Capabilities
|
||||
|
||||
```kotlin
|
||||
val tx: NwcTransaction = ...
|
||||
|
||||
NwcTransactionState.isSettled(tx.state) // true for "SETTLED" or "settled"
|
||||
NwcTransactionState.isPending(tx.state) // true for "PENDING" or "pending"
|
||||
NwcTransactionState.isFailed(tx.state) // true for "FAILED" or "failed"
|
||||
NwcTransactionState.isAccepted(tx.state) // true for "ACCEPTED" or "accepted"
|
||||
```
|
||||
|
||||
### 7. URI Persistence
|
||||
|
||||
To save/restore the NWC connection:
|
||||
|
||||
```kotlin
|
||||
// Save
|
||||
val nip47URI: Nip47WalletConnect.Nip47URI = nwcConfig.denormalize()!!
|
||||
val json = Nip47WalletConnect.Nip47URI.serializer(nip47URI)
|
||||
|
||||
// Restore
|
||||
val restored = Nip47WalletConnect.Nip47URI.parser(json)
|
||||
val normalized = restored.normalize()!!
|
||||
```
|
||||
|
||||
## Implementing a Wallet Service
|
||||
|
||||
A wallet service receives NWC requests from clients, processes them (e.g.,
|
||||
pays invoices via a Lightning node), and sends back responses.
|
||||
|
||||
### 1. Publish Capabilities
|
||||
|
||||
Advertise which methods your wallet supports by publishing a kind `13194`
|
||||
event:
|
||||
|
||||
```kotlin
|
||||
val capabilities = listOf(
|
||||
NwcMethod.PAY_INVOICE,
|
||||
NwcMethod.GET_BALANCE,
|
||||
NwcMethod.GET_INFO,
|
||||
NwcMethod.MAKE_INVOICE,
|
||||
NwcMethod.LOOKUP_INVOICE,
|
||||
NwcMethod.LIST_TRANSACTIONS,
|
||||
)
|
||||
|
||||
val infoTemplate = NwcInfoEvent.build(
|
||||
capabilities = capabilities,
|
||||
capabilities = listOf(NwcMethod.PAY_INVOICE, NwcMethod.GET_BALANCE),
|
||||
encryptionSchemes = listOf("nip04", "nip44_v2"),
|
||||
notificationTypes = listOf(
|
||||
NwcNotificationType.PAYMENT_RECEIVED,
|
||||
NwcNotificationType.PAYMENT_SENT,
|
||||
),
|
||||
notificationTypes = listOf(NwcNotificationType.PAYMENT_RECEIVED),
|
||||
)
|
||||
// Sign with wallet signer: walletSigner.sign(infoTemplate)
|
||||
```
|
||||
|
||||
### 2. Receive and Parse Requests
|
||||
|
||||
Subscribe to kind `23194` events on your relay, filtered by your wallet
|
||||
service pubkey in the `p` tag. When a request arrives:
|
||||
#### 2. Parse Requests and Build Responses
|
||||
|
||||
```kotlin
|
||||
// requestEvent is a LnZapPaymentRequestEvent (kind 23194)
|
||||
val request: Request = requestEvent.decryptRequest(walletSigner)
|
||||
|
||||
when (request) {
|
||||
is PayInvoiceMethod -> {
|
||||
val bolt11 = request.params?.invoice
|
||||
val amount = request.params?.amount // optional override in millisats
|
||||
// Process payment via your Lightning node...
|
||||
}
|
||||
is GetBalanceMethod -> {
|
||||
// Query your Lightning node for balance...
|
||||
}
|
||||
is MakeInvoiceMethod -> {
|
||||
val amount = request.params?.amount // in millisats
|
||||
val description = request.params?.description
|
||||
// Create invoice via your Lightning node...
|
||||
}
|
||||
is ListTransactionsMethod -> {
|
||||
val limit = request.params?.limit
|
||||
val offset = request.params?.offset
|
||||
val type = request.params?.type // "incoming" or "outgoing"
|
||||
// Query transaction history...
|
||||
}
|
||||
is GetInfoMethod -> {
|
||||
// Return node info...
|
||||
}
|
||||
is GetBudgetMethod -> {
|
||||
// Return budget info...
|
||||
}
|
||||
// ... handle other methods
|
||||
}
|
||||
```
|
||||
|
||||
### 3. Build and Send Responses
|
||||
|
||||
Use `LnZapPaymentResponseEvent.createResponse()` to create encrypted
|
||||
response events:
|
||||
|
||||
```kotlin
|
||||
// Success response for get_balance
|
||||
// Build response
|
||||
val balanceResponse = GetBalanceSuccessResponse(
|
||||
GetBalanceSuccessResponse.GetBalanceResult(balance = 2100000L) // in millisats
|
||||
GetBalanceSuccessResponse.GetBalanceResult(balance = 2100000L)
|
||||
)
|
||||
val responseEvent = LnZapPaymentResponseEvent.createResponse(
|
||||
response = balanceResponse,
|
||||
requestEvent = requestEvent,
|
||||
signer = walletSigner,
|
||||
)
|
||||
// Send responseEvent to the relay
|
||||
|
||||
// Success response for pay_invoice
|
||||
val payResponse = PayInvoiceSuccessResponse(
|
||||
PayInvoiceSuccessResponse.PayInvoiceResultParams(
|
||||
preimage = "0123456789abcdef",
|
||||
fees_paid = 100L,
|
||||
)
|
||||
)
|
||||
val payResponseEvent = LnZapPaymentResponseEvent.createResponse(
|
||||
response = payResponse,
|
||||
requestEvent = requestEvent,
|
||||
signer = walletSigner,
|
||||
)
|
||||
|
||||
// Error response
|
||||
val errorResponse = NwcErrorResponse(
|
||||
@@ -353,60 +256,46 @@ val errorEvent = LnZapPaymentResponseEvent.createResponse(
|
||||
)
|
||||
```
|
||||
|
||||
To use NIP-44 encryption for responses:
|
||||
#### 3. Send Notifications
|
||||
|
||||
```kotlin
|
||||
val responseEvent = LnZapPaymentResponseEvent.createResponse(
|
||||
response = balanceResponse,
|
||||
requestEvent = requestEvent,
|
||||
signer = walletSigner,
|
||||
useNip44 = true,
|
||||
)
|
||||
```
|
||||
|
||||
### 4. Send Notifications
|
||||
|
||||
Push notifications to clients for payment events:
|
||||
|
||||
```kotlin
|
||||
// Payment received notification
|
||||
val notification = PaymentReceivedNotification(
|
||||
notification = NwcTransaction(
|
||||
type = NwcTransactionType.INCOMING,
|
||||
state = NwcTransactionState.SETTLED,
|
||||
invoice = "lnbc...",
|
||||
amount = 50000L, // in millisats
|
||||
payment_hash = "abc123",
|
||||
settled_at = TimeUtils.now(),
|
||||
created_at = TimeUtils.now(),
|
||||
),
|
||||
)
|
||||
val notifEvent = NwcNotificationEvent.createNotification(
|
||||
notification = notification,
|
||||
notification = PaymentReceivedNotification(
|
||||
notification = NwcTransaction(
|
||||
type = NwcTransactionType.INCOMING,
|
||||
state = NwcTransactionState.SETTLED,
|
||||
invoice = "lnbc...",
|
||||
amount = 50000L,
|
||||
payment_hash = "abc123",
|
||||
settled_at = TimeUtils.now(),
|
||||
created_at = TimeUtils.now(),
|
||||
),
|
||||
),
|
||||
clientPubkey = clientPubkeyHex,
|
||||
signer = walletSigner,
|
||||
)
|
||||
// Send notifEvent to the relay
|
||||
```
|
||||
|
||||
### 5. Build Transaction Objects
|
||||
## Transaction State Helpers
|
||||
|
||||
Transactions are used across multiple response types:
|
||||
Transaction states from different wallet implementations may use different
|
||||
casing. Use the case-insensitive helpers:
|
||||
|
||||
```kotlin
|
||||
val transaction = NwcTransaction(
|
||||
type = NwcTransactionType.INCOMING, // or OUTGOING
|
||||
state = NwcTransactionState.SETTLED, // PENDING, SETTLED, FAILED, ACCEPTED
|
||||
invoice = "lnbc50n1...",
|
||||
description = "Coffee payment",
|
||||
payment_hash = "abc123def456",
|
||||
preimage = "fedcba654321",
|
||||
amount = 50000L, // in millisats
|
||||
fees_paid = 100L, // in millisats
|
||||
created_at = 1693876497L, // unix timestamp
|
||||
settled_at = 1693876500L,
|
||||
expires_at = 1694876497L,
|
||||
)
|
||||
NwcTransactionState.isSettled(tx.state) // true for "SETTLED" or "settled"
|
||||
NwcTransactionState.isPending(tx.state) // true for "PENDING" or "pending"
|
||||
NwcTransactionState.isFailed(tx.state) // true for "FAILED" or "failed"
|
||||
NwcTransactionState.isAccepted(tx.state) // true for "ACCEPTED" or "accepted"
|
||||
```
|
||||
|
||||
## URI Persistence
|
||||
|
||||
```kotlin
|
||||
// Save
|
||||
val json = Nip47WalletConnect.Nip47URI.serializer(nwcConfig.denormalize()!!)
|
||||
|
||||
// Restore
|
||||
val restored = Nip47WalletConnect.Nip47URI.parser(json).normalize()!!
|
||||
```
|
||||
|
||||
## Error Codes
|
||||
@@ -431,13 +320,8 @@ val transaction = NwcTransaction(
|
||||
|
||||
NWC supports two encryption schemes:
|
||||
|
||||
- **NIP-04** (default): Set `useNip44 = false` in event builders
|
||||
- **NIP-44 v2**: Set `useNip44 = true` in event builders
|
||||
|
||||
When building requests with NIP-44, the event includes an `encryption` tag:
|
||||
```
|
||||
["encryption", "nip44_v2"]
|
||||
```
|
||||
- **NIP-04** (default): `Nip47Client(useNip44 = false)` or `useNip44 = false` in event builders
|
||||
- **NIP-44 v2**: `Nip47Client(useNip44 = true)` or `useNip44 = true` in event builders
|
||||
|
||||
Clients can check a wallet's supported encryption via the info event:
|
||||
```kotlin
|
||||
@@ -454,8 +338,6 @@ For apps handling many concurrent NWC events, use the built-in LRU caches:
|
||||
val requestCache = NostrWalletConnectRequestCache(signer)
|
||||
val responseCache = NostrWalletConnectResponseCache(signer)
|
||||
|
||||
// These cache up to 50 decrypted results and handle
|
||||
// async retry logic for permission dialogs and timeouts.
|
||||
val request: Request? = requestCache.decryptRequest(requestEvent)
|
||||
val response: Response? = responseCache.decryptResponse(responseEvent)
|
||||
```
|
||||
|
||||
Reference in New Issue
Block a user