-
Notifications
You must be signed in to change notification settings - Fork 598
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
NIP-57: Lightning Zaps #224
Conversation
Co-authored-by: 0xtr <oxtrr@protonmail.com>
nice! awesome spec! Will work on implementing this. |
Can we remove the |
probably, I can't remember why I had them separate. |
I kinda rushed this and might be underspecified, let me know if anything seems unclear. |
maybe for temporarily disabling it while still allowing clients to validate older zaps? I think I had some kind of reasoning along those lines. |
Makes sense. |
I don't understand the need for the encrypted zap request, since HTTPS calls are already encrypted. What have I misunderstood? |
This would allow you to hide who created the invoice. you could still tally zaps and only the person receiving the zap could see who it was from. The description would be an encrypted |
Well it would be slightly more complicated, it would have to be a kind 9733 note with a random key, or potentially just a json blob with no key; with relays encrypted to the zapper (to reduce fingerprinting), in this json is a 9734 note encrypted to the user getting zapped. I have this all thought out but didn't want to complicate this spec with it just yet. |
|
||
1. Calculate the lnurl pay request url for a user from the lud06 or lud16 field on their profile | ||
|
||
2. Fetch the lnurl pay request static endpoint (`https://host.com/.well-known/lnurlp/user`) and gather the `allowsNostr` and `nostrPubkey` fields. If `allowsNostr` exists and it is `true`, and if `nostrPubkey` exists and is a valid BIP 340 public key, associate this information with the user. The `nostrPubkey` is the `zapper`'s pubkey, and it is used to authorize zaps sent to that user. |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
2. Fetch the lnurl pay request static endpoint (`https://host.com/.well-known/lnurlp/user`) and gather the `allowsNostr` and `nostrPubkey` fields. If `allowsNostr` exists and it is `true`, and if `nostrPubkey` exists and is a valid BIP 340 public key, associate this information with the user. The `nostrPubkey` is the `zapper`'s pubkey, and it is used to authorize zaps sent to that user. | |
2. Fetch the lnurl pay request static endpoint (`https://host.com/.well-known/lnurlp/user`) and gather the `allowsNostr` and `nostrPubkey` fields. If `allowsNostr` exists and it is `true`, and if `nostrPubkey` exists and is a valid BIP 340 public key, 32 bytes, hex-encoded, associate this information with the user. The `nostrPubkey` is the `zapper`'s pubkey, and it is used to authorize zaps sent to that user. |
I am excited to see the interest of deeply integrating lightning payments into Nostr. The name zaps is great! Here are a few thoughts/questions: Do I understand it correctly that the zap request should be stored in the invoice description? Or only the hash of the zap request in the invoice description_hash? "Parse the bolt11 description as a JSON nostr note" indicates that the whole nostr node should be saved in the invoice description. Would it be possible to reuse LUD18 payerdata for this? afaik LUD18 is already generic and allows the storage generic payer data. It is also committed in the description_hash. Would it be possible to allow the client to publish the zap? This would remove the requirement that LNURL implementations/service providers need to implement Nostr connections. The smaller the usage specific requirement is the more tools/service providers will be able to be used. I am worried that this is a too big limitation. IMO ideally the LNURL spec should not have any social network specific requirements and is independent. The main data that would be needed is the preimage, correct? With the preimage and the payment request/hash the validation can also be done. Thanks for pushing this forward! This can bring the value4value idea to so many more usecases! |
I was pretty happy with this NIP, but after thinking about it for many minutes, this question has occurred to me: is there a reason for reusing LNURL endpoints even though this is not an LNURL payment at all? Wouldn't it be more elegant and much simpler to decouple this entirely from LNURL and use an independent spec (and a different Moreover the communication could be reduced to a single step. Could be something like: A user sets a field in their Nostr metadata like: Then a client can call
and get back {"pubkey": "<hex pubkey of the zapper>"} And when sending a zap the client would call:
and immediately get an invoice back, check that the description hash matches against the event id of the The only reason I can see for keeping the lud06/lud16 things in place and reusing them are that people don't have to change their metadata and can start getting zaps immediately as soon as their zapper provider (currently LNURL provider) implements this NIP. But I think this reason is not good enough and that a dedicated protocol that doesn't ever say anything about LNURL such as above would be less confusing and easier to implement for everybody. |
Another idea is to embed the zapper pubkey in the metadata of the user who is receiving the zaps directly, so the field could be something like Now I am thinking: why restrict the number of zappers a user can have to only one? Maybe I am crazy, but users may want to have more than one, as fallbacks. So the field could be Or maybe it is even better to put these things in yet another dedicated replaceable event, as explained by @staab at #218 (comment), that would mitigate the issue of unwanted metadata overwrites and then only clients that implemented zaps could fetch that specific event to the get zapper addresses for people. |
The problem with my spec suggestion above is that it assumes the It is probably better that the specified zapper URL be just an arbitrary URL and clients just POST to that URL directly, without the OK, now I'll shut up. |
I think being able to build on all the adoption of LNURL/LUD16 out there is a great advantage. Imo ideally people can use their current setup as much as possible and developers also don't need to start from scratch again building nostr LN support. So I am +1 for LNURL What exactly do we miss from LNURL? |
Would it be possible to allow the client to publish the zap? - I like Bhumi's point on having this option. There are many wallets such as Strike don't have LNURL implemented. Zaps break if wallet does not support it. There should be a manual option for zapper user to input the preimage after the payment and the client creates kind 9735 event on behalf of the zapper user. This eliminates dependency on wallets implementing LNURL and Zapper specs. |
I don't think this is true. The Nostr client requests a Bolt11 invoice from the server which all wallets should be able to pay. |
The only downside I can think of is that it would allow Carol to zap Alice at alice+bob_pubkey@ln.com. This would appear as a zap from Bob to Alice, but it's like Carol donated to Alice in Bob's name. Who would mind that? |
|
||
b. It MUST have tags | ||
|
||
c. It MUST have at least one p-tag |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
c. It MUST have at least one p-tag | |
c. It MUST have one p-tag |
Zaps cannot be sent to multiple users with one request
I love the zaps - as everybody :) seeing the excitement and interest on this is a great sign for monetization options. As mentioned I am a bit concerned about the Nostr specific requirements for LNURL providers. This adds implementation complexity and we also create a Nostr island that is no longer interoperable. To me reusabiltity and interoperability is an important goal. If people can use their existing lightning setup to receive payments on Nostr then this is favorable and simplifies and especially speeds up adoption. It took years for many providers and tools to implement LNURL-pay and we are still far from seeing its full potential. I also think the lightning related spec should be universal. Being able to use it also for other areas like for example blogging makes it more powerful. If we couple this closely to Nostr and if it is only useful within Nostr then imo it's better to just have a Nostr specific setup (as @fiatjaf says). From my understanding the requirements are:
is this correct? With LUD18 we have the protocol to pass a payerData record to the LNURL server A potential protocol flow:Client side
If the invoice is passed to an app the preimage can not be passed back. In this case the LNURL-verify can be used to check the payment status and retrieve details like the preimage from the LNURL-server. (this is what @v0l proposes above)
LNURL Server side
As mentioned I generally think that the LNURL-pay spec should not contain any application/usage specific requirements. From a LNURL point of view Nostr is "just" one usage. I think with some abstraction this can be done and does not require high-level Nostr coupling. P.S. |
Thanks Bumi for the approach. I really like multiple options in who is creating the note kind - 9735. Nostr clients should determine this and should not force lightning providers to create this. It is not important at all on who is creating this note kind 9735 only the content in this note such as preimage, bolt11 and LNURL-verify is important. This also opens up another scenario/use case for anonymous zaps where the zap sender pay for sats but don't want his pub key associated with it. Think of Canadian truck drivers protest. To keep anonymity, after the payment is successful, Nostr client can create this note with a random private key and destroy the key after it is posted. Zap receiver or the public don't know who zapped it but they receive sats which is powerful. Am also up for implementing multiple approaches to see which one is liked and adopted by the users. Thanks to @jb55 and @v0l for pushing a working version into the wild and seeing the reaction. |
In fact it is. When displaying the zaps, clients are trusting that they come from a reputable provider -- or from the direct zap receiver themselves. If anyone can create an event then I can create a trillion zap events myself and fake that I have tipped the entire universe. |
Don't trust but verify...! if you are trusting that they came from a reputable provider then there is no difference between today's world (Apple trusting VisaMastercard). We are simply replacing the current overlords with new ones. You can argue that the new overlords are better but that is not the point. The lightning/wallet providers should provide a verifiable link for the payment LN-URL which can be embedded on the note. |
About @bumi's proposal above, I want to say that it would be nice to have it realized, but I am afraid the added complexity wouldn't be worth the trouble. @jb55 has actually implemented a zap flow using LUD-18 as far as I know, but later decided to drop it, which I think was sensible (I just don't understand why he didn't go all the way and dropped everything, which is what I want to happen). The complexity I mention above is mostly the reliance on the There is also the problem that the payer is the one publishing the zap, which opens the door for anyone to forge zaps @starbackr-dev (unless we introduce another requiment, that the invoice is signed by the receiver nostr public key or sometihng like that, i.e. more complexity). I think the receiver should be the one signing the Considering all this I believe the most sensible path forward is to get rid of lnurl entirely and make a simpler protocol that is specific to Nostr. |
the client only needs to be able to verify it. In my proposal this is done by asking the receiver (reputable provider) - not through a nostr note signing but through the same method we actually receive the invoice from the receiver. (with a HTTP call) both LUD-18 and LNURL-verify would be optional. LUD-18 is only needed to tell the recipient for which note that payment was. and LNURL-verify would only be for clients to check if the claimed payment actually happened. @fiatjaf can you describe the complexity that you see? If we would do that, I am with @fiatjaf that this should no longer be LNURL. That spec should be universal and not have such a tight coupling to something else like some social networking. |
what makes something lnurl vs not lnurl? I agree it's not lnurl, all I'm doing is piggy-backing on the lnurl endpoint since these servers are already setup to fetch invoices from backends. the only thing that changes is the metadata gets swapped out with a nostr note. This is the entirety of the change required on my lnurl server: From 4677a23f3c776326ee4f622d45cd54127284e864 Mon Sep 17 00:00:00 2001
From: William Casarin <jb55@jb55.com>
Date: Sun, 8 Jan 2023 17:11:26 -0800
Subject: [PATCH] accept nostr notes
---
index.js | 86 +++++++++++++++++++++++++++++++++++++++++++----
package.json | 2 +-
public/index.html | 2 +-
3 files changed, 81 insertions(+), 9 deletions(-)
diff --git a/index.js b/index.js
index d836358..32de30c 100644
--- a/index.js
+++ b/index.js
@@ -45,12 +45,12 @@ async function make_request(auth, method, params) {
}
-async function make_invoice(opts, amount)
+async function make_invoice(opts, amount, extra)
{
try {
const res = await make_request(opts, "invoice", {
amount_msat: amount || "any",
- description: make_metadata_str(opts),
+ description: make_metadata_str(opts, extra),
deschashonly: true,
label: crypto.randomUUID().toString(),
})
@@ -85,8 +85,11 @@ function make_metadata(opts) {
return metadata
}
-function make_metadata_str(opts) {
- return JSON.stringify(make_metadata(opts))
+function make_metadata_str(opts, extra) {
+ if (extra && extra.nostr)
+ return JSON.stringify(extra.nostr)
+
+ return JSON.stringify(make_metadata(opts, extra))
}
async function find_metadata(user)
@@ -125,6 +128,23 @@ function invalid_username(res) {
return json_err(res, "Bad username", 400)
}
+function get_nostr_note(parsed) {
+ let nostr = parsed.searchParams.get('nostr')
+ if (nostr == null)
+ return
+
+ try {
+ nostr = JSON.parse(nostr)
+ } catch (e) {
+ throw new Error(`nostr parameter must be a nostr note: ${e}`)
+ }
+
+ const validated = validate_note(nostr)
+ if (validated !== true)
+ throw new Error(validated)
+ return nostr
+}
+
async function handle_static_payreq(parsed, req, res)
{
let user = user_from_wellknown(parsed.pathname)
@@ -137,13 +157,26 @@ async function handle_static_payreq(parsed, req, res)
if (metadata === null)
return user_not_found(res)
- const resp = {
+ let nostr
+ try {
+ nostr = get_nostr_note(parsed)
+ } catch (e) {
+ return json_err(res, e.toString(), 400)
+ }
+ const extra = { nostr }
+
+ let resp = {
status: "OK",
tag: "payRequest",
minSendable: 1,
maxSendable: 10000000000,
callback: `https://sendsats.lol/@${user}`,
- metadata: make_metadata_str(metadata),
+ metadata: make_metadata_str(metadata, extra),
+ }
+
+ if (user === "jb55") {
+ resp.allowsNostr = true
+ resp.nostrPubkey = "9630f464cca6a5147aa8a35f0bcdd3ce485324e732fd39e09233b1d848238f31"
}
return json_ok(res, resp)
@@ -161,12 +194,21 @@ async function handle_payreq(parsed, req, res)
if (!amount)
return json_err(res, "Missing amount", 400)
+
+ let nostr
+ try {
+ nostr = get_nostr_note(parsed)
+ } catch (e) {
+ return json_err(res, e.toString(), 400)
+ }
+ const extra = { nostr }
+
const metadata = await find_metadata(user)
if (metadata === null)
return user_not_found(res)
try {
- const pr = await make_invoice(metadata, amount)
+ const pr = await make_invoice(metadata, amount, extra)
const routes = []
json_ok(res, {pr, routes})
} catch (e) {
@@ -316,6 +358,36 @@ async function serve_page(pathname, req, res)
}
+function validate_note(note)
+{
+ if (note.kind !== 9734)
+ return "Expected tip request note (kind 9734)"
+
+ if (!note.tags)
+ return "No tags found?"
+
+ // Make sure we only have one p tag
+ const ptags = note.tags.filter(t =>
+ t && t.length && t.length >= 2 && t[0] === "p")
+
+ if (ptags.length !== 1)
+ return "None or multiple p tags found"
+
+ // Make sure we have 0 or 1 etag (for note tipping)
+ const etags = note.tags.filter(t =>
+ t && t.length && t.length >= 2 && t[0] === "e")
+ if (!(etags.length === 0 || etags.length === 1))
+ return "Expected 0 or 1 e tags"
+
+ // Look for the relays tag, the wallet will broadcast to these relays
+ const relays_tag = note.tags.find(t =>
+ t && t.length && t.length >= 2 && t[0] === "relays")
+ if (!relays_tag)
+ return "No relays tag found"
+
+ return true
+}
+
function handle_request(opts, req, res)
{
console.log("%s - %s", req.method, req.url)
diff --git a/package.json b/package.json
index eba4435..d68a4c0 100644
--- a/package.json
+++ b/package.json
@@ -1,7 +1,7 @@
{
"name": "sendsats.lol",
"description": "sendsats.lol",
- "version": "0.1.0",
+ "version": "1.2.2",
"repository": {
"url": "https://github.com/jb55/sendsats.lol"
},
diff --git a/public/index.html b/public/index.html
index 3f7cbc0..0c26a25 100644
--- a/public/index.html
+++ b/public/index.html
@@ -102,7 +102,7 @@
<script src="js/qrcode.js?v=3" > </script>
<script src="js/lnsocket.js?v=3" > </script>
- <script src="js/index.js?v=10" > </script>
+ <script src="js/index.js?v=11" > </script>
</body>
</html>
--
2.39.1
The proposed alternative seems like I would have to rejig everything for reasons which I don't understand. |
afaik snort, damus and amethyst have already implemented the original spec |
The change is small, but it introduces spaghetti in your lnurl server. And it is only small because you already have an lnurl server. Now for people that want to make zap-only servers, they will have to make a full lnurl server first, then introduce your changes -- while if the protocol didn't rely on anything related to lnurl their complete job of implementing zaps would be much smaller. The same applies to clients that only want to implement zaps and not lnurl donations. Since zaps are a much more natural way of doing things on Nostr I imagined the lnurl donation stuff would end up dying, which would be a good thing probably. |
Another problem with the way this NIP was made is that now users can't have separate zap and lnurl services on their profile. They could choose to have an lnurl for donations -- and that same service doesn't support zaps, but they can't. Or they could decide to use a service that just supports zaps, not lnurl, and now people will see on clients that they accept lnurl and just be frustrated when paying. The proposal will either make user experience worse everywhere or make it so only providers implementing both protocols will be chosen by users, which will have a centralizing effect. It's naïve to assume only services implementing lnurl today will implement zap support and also that 100% of those will implement zap support, which are the two requirements for this NIP proposal to be a good idea. |
According to @fiatjaf comments, both approaches appear to have equal pros and cons. However, @jb55 stated that the specifications have already been implemented in three popular Nostr clients. So I went ahead and started putting it into action. It only took a couple of hours for me to change the backend so that LNURL will continue to work for non-Nostr use cases and posting type 9735 will just be implemented for invoices created by Nostr clients. It is preferable to test this in public to obtain user feedback rather than iterating inside github. |
Turns out this was settled long before the PR was opened, there were no answers to any the feedback presented here at all. Committing directly to master would have been better and would have wasted less time from others. |
Agree. Very unfortunate. |
Reading this PR makes me think why the rush? Nostr is developing fast, that's great, but more "PRs" like this and Nostr might end up in a complete mess. It suppose it is good practice to propose something and implement it in parallel to do a reality-check, but if the discussion shows that some NIP has flaws early developers need to re-do the work and not stick to it. I wonder how this will turn out... To my understanding (as a interested user, not as a developer) I find it confusing to have a working lnurl service, but zaps do not work. And if I want to test zaps with a supported provider, I can't use my working lnurl service. |
There was a proposal to address exactly that: #244 |
There were also other proposals above that made zaps not interfere with LNURL, or at least be backwards compatible with existing LNURL services. But hey, this was easy to implement bro, don't understand what the fuss is all about. |
This NIP defines a new note type called a lightning zap of kind
9735
. These represent paid lightning invoice receipts sent by a lightning node called thezapper
. We also define a another note type of kind9734
which arezap request
notes, which will be described in this document.Having lightning receipts on nostr allows clients to display lightning payments from entities on the network. These can be used for fun or for spam deterrence.
https://github.com/nostr-protocol/nips/blob/zaps/57.md