-
Notifications
You must be signed in to change notification settings - Fork 706
NIP-47 Nostr Wallet Connect #406
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
Conversation
|
Big fan! Especially because this will enable clients to do more complex payments (like splits) with good UX and without having a native wallet. Currently working on a POC. |
|
What exactly was changed:
|
|
I will wait for @vitorpamplona to approve it. |
|
The text is better. But I am not a fan of this One-event-kind-that-contains-all-message-types-via-json-in-content trend. I am ok in merging Parseable JSONs with sub-message types is damaging to the simplicity of Nostr and should be avoided at all costs. That being said, If I am overridden, I will change Amethyst to this new style. |
|
PS: I know this is hypocritical, since my NIP-82 (#357) draft also has a sub-message type as a json in |
|
EDIT: Disregard, my autism got the better of me 🫤 |
It prevents metadata leaks. It also means that you are basically yelling in the void if you use a new kind -- with the command name in the single kind, that can be detected and an unknown command error can be sent. There also may need to be extensions to the just BOLT11 format in the future -- not using JSON makes it harder. I would understand your stance on using multiple kinds if this was meant to be a generic spec for everything encrypted, or the content was unencrypted. |
|
Thanks for starting the discussion and opening the PR.
|
Sorry in advance as I may have misunderstood things. But if I got it right, the private key is the I'm a bitcoin noob so maybe the lightning node/wallet service won't auto-pay and instead is expected to always display a confirmation dialog for authorization at each pay_invoice request so no problem? |
Yes
The idea is to only show it once when you register for a new session in the wallet and do it either via QR code or app-to-app deep link calls in a phone. In that way, the private key is not "visible". The app reads it and places it inside it's encrypted storage, together with the person's nsec.
That's how it's coded in Amethyst. If they have a Wallet Connect key, it tries to pay through there and if it doesn't, or if it fails, it opens a regular wallet selection dialog in the phone. |
|
@vitorpamplona thanks for explaining
My main doubt is at that quoted moment. Is the connected wallet expected to just check if the pay_invoice request event (e.g. sent from Amethyst) is valid and has the pubkey generated from the right secret (from the query param) and if true will pay the invoice without user having to confirm anything? My concern isn't Amethyst but some malicious or vulnerable web app. Like if said app has the secret, it could send many pay_invoice requests at 4a.m. that will be automatically accepted and payed while the user is sleeping, something like that. So the user has to trust the app just like it trusts when pasting a nsec (instead of using NIP-07 browser extension, for instance). Edit: Talking about Amethyst, I see one can paste pubkey and relay but not the secret, so I guess you are using the user private key instead of what comes from the NIP-47 "secret" param (which could be a different one). |
Yep, not only trust the app but also trust the wallet connect service on the other side. The wallet connect service can add protections to block transactions that are too big or too frequent. The main point here is indeed that the Nostr client can control auto-pay. This allows clients to create all types of Nostr-native subscription services with frequent payments without having to confirm with the wallet at every time. For instance, I wanted to do a nightly zap to all posts the user spent any time on, with a total daily allowance split proportionally to the amount of time spent per post. The app would just automatically send these if users want. |
|
I like this |
It is effectively not shown to the user in a way that asks them to back it up. The user probably also doesn't store the URL somewhere since it's easier to just get a new one.
We still should merge it, in my opinion.
Please propose a suggestion in a review to make it consistent with NIP-46. I believe that it will help further extensibility.
The client would subscribe to messages from their zap service with their own secret's public key as the p tag. You do not need to re-subscribe.
ack |
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.
I think it makes sense to include the invoice description in the request. Core lighting has deprecated paying invoices without being able to verify the description hash though enforcing this has been reverted, I still think its best to include it.
47.md
Outdated
| Request: | ||
| ```jsonc | ||
| { | ||
| "invoice": "lnbc50n1..." // BOLT11 invoice, string |
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.
| "invoice": "lnbc50n1..." // BOLT11 invoice, string | |
| "invoice": "lnbc50n1...", // BOLT11 invoice, string | |
| "description": "<invoice description>" // Invoice description, string |
|
@thesimplekid I believe this is a whole other discussion. For my opinion, see ElementsProject/lightning#6092 (comment) |
|
|
||
| Paying zaps should be possible without the user needing to open a different app to only pay a Lightning invoice. | ||
| This NIP describes a way for users to control a remote Lightning node or a custodial Lightning wallet. When self-hosting, this setup does not require the user to run their own server, thereby bypassing certain hurdles that are commonly encountered when trying to connect to a Lightning node remotely. | ||
|
|
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.
This NIP is designed to forward the responsibility of executing lightning payments to specialized Nostr micro apps whose sole purpose is to connect with the user's desired lightning service, generally using the service's proprietary API, and establish security controls to approve such transactions. Regular Nostr Clients are then exempt from implementing payment interfaces with each lightning provider out there and from implementing security settings to authorize certain payment types.
| ## Nostr Wallet Connect URI | ||
| **client** discovers **wallet service** by scanning a QR code, handling a deeplink or pasting in a URI. | ||
|
|
||
| The **wallet service** generates this connection URI with protocol `nostr+walletconnect:` and base path it's hex-encoded `pubkey` with the following query string parameters: |
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.
There is no + in the current implementations: nostrwalletconnect:
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.
It is intentional. Signaling breaking change and makes it clearer it's nostr.
47.md
Outdated
| Response: | ||
| ```jsonc | ||
| { | ||
| "status": "ok", // status, "ok" | "error" |
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.
Should we use the OK of the base Nostr protocol for success? Why duplicate this here?
| "status": "ok", // status, "ok" | "error" | ||
| "event": "0123456789abcdef...", // event the command is in response to, string | ||
| "data": { // response data | ||
| "preimage": "0123456789abcdef..." // command-related data |
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.
Does anybody need this preimage? I don't really use this in the current implementation
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.
There was some talk about preimage being used to decrypt content for other uses.
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.
It acts like a proof of payment and I think Will said that it would allow for an update to NIP-57 where the client dispatches the Zap event.
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.
YES, the preimage is needed and allows all kind of things, starting with a simple check if the that requested invoice was actually paid by comparing the payment hash to the preimage.
I also still think that NIP-57 should events published by the clients and with the preimage the same proof of payment can be provided. (making it much easier for the LNURL servers)
47.md
Outdated
| Response: | ||
| ```jsonc | ||
| { | ||
| "balance": 100000 // balance in msat, int |
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.
In typed languages, you have to inform the object you want to map your JSON into before calling the parser. In this proposal, there is no way to know if the JSON is going to be a balance or a result beforehand, which makes it impossible to correctly use in typed languages. Of course, typed languages can always navigate the JSON map manually, but it's not a great implementation.
47.md
Outdated
| ```jsonc | ||
| { | ||
| "status": "ok", // status, "ok" | "error" | ||
| "event": "0123456789abcdef...", // event the command is in response to, string |
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.
This event should be an e tag, not a json property.
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.
Yes. Nostr allows for native responding through the e tag, so why not use the e tag?
47.md
Outdated
| The **wallet service** generates this connection URI with protocol `nostr+walletconnect:` and base path it's hex-encoded `pubkey` with the following query string parameters: | ||
|
|
||
| - `relay` Required. URL of the relay where the **wallet service** is connected and will be listening for events. May be more than one. | ||
| - `secret` Required. 32-byte randomly generated hex encoded string. The **client** should use this to sign events when communicating with the **wallet service**. |
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.
If secret is required, the text should read:
The client MUST use this to sign events when communicating with the wallet service. The user's private key should NEVER be used in this NIP.
|
Alright, thanks for the extra context @vitorpamplona and fine for me to split things up. I am glad you recognize "request & response over nostr" is a common need. In the end, my only issue may be for the To me, "wallet" is much more than lightning nodes and is a bit misleading imho as not all wallets are nodes. Do you think a different prefix/name can be considered at this point? ie. |
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.
Overall my main feedback is that we should make the JSON structure the same as NIP-46 and not add too much bloat to the NIP.
47.md
Outdated
| - `NIP-47 request`: 23194 | ||
| - `NIP-47 response`: 23195 | ||
|
|
||
| Both the request and response events SHOULD only contain one `p` tag, containing the public key of the **wallet service** if this is a request, and the public key of the **client** if this is a response. |
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.
I still don't get why the response should not contain an e tag to refer to the event it is responding to?
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.
+1 I guess this would make it easier for clients to reference it?
or is this too much metadata?
47.md
Outdated
| Request: | ||
| ```jsonc | ||
| { | ||
| "cmd": "pay_invoice", // command, string |
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.
| "cmd": "pay_invoice", // command, string | |
| "method": "pay_invoice", // command, string | |
| "params" : ["lnbc..."] |
So the payload structure is at least the same as NIP-46.
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.
+1 and it's also more like JSONPRC then and not using yet another structure.
|
|
||
| Response: | ||
| ```jsonc | ||
| { |
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.
The response should have the same structure as NIP-46:
| { | |
| { | |
| "id": <request_id>, | |
| "result": <anything>, | |
| "error": <reason> | |
| } |
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.
In JSONRPC error is an object with "code" and "message"
code's are good for apps and messages are good for humans.
if we go the generic route maybe we should allow this?
| The data field SHOULD contain a `message` field with a human readable error message and a `code` field with the error code if the status is `error`. | ||
|
|
||
| ### Error codes | ||
| - `RATE_LIMITED`: The client is sending commands too fast. It should retry in a few seconds. |
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.
Error codes should be in the error field to be consistent with NIP-46.
47.md
Outdated
| Response: | ||
| ```jsonc | ||
| { | ||
| "balance": 100000, // balance in msat, int |
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.
In order to make this more consistent with NIP-46, we should have a describe call with the same response as NIP-46. Balance should be a seperate command.
47.md
Outdated
| Response: | ||
| ```jsonc | ||
| { | ||
| "balance": 100000, // balance in msat, int |
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.
Please use sats and not msats. Nobody likes msats and it overcomplicates things.
47.md
Outdated
| } | ||
| ``` | ||
|
|
||
| ### `create_invoice` |
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.
I believe everything outside pay_invoice should be marked optional and an extension of NIP-47. Otherwise the NIP will become too bloated.
47.md
Outdated
| Request: | ||
| ```jsonc | ||
| { | ||
| "amount": 1000, // amount in msat, int |
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.
Use sats instead of msats.
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.
definitely should be in msats, no reason to lose granularity
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.
can we then call it msats?
it confuses so many people, most people think in sats and then run into strange errors.
I think CLN also uses msats in their params.
47.md
Outdated
| Response: | ||
| ```jsonc | ||
| { | ||
| "preimage": "0123456789abcdef..." // preimage after payment, string |
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.
Should use NIP-46 payload structure.
Actually, I agree with @vitorpamplona to account for typed data structures and polymorphic objects. I agree JSON/payloads share the same format between nips, I am happy to amend NIP-46 to follow anything that comes out of NIP-47, instead of the other way around. |
|
New learning: It should be a regular replaceable event. The wallet connect service only signs them when the balance/info changes. Nostr clients simply subscribe to those events as usual. There is no need for request/response specification. It doesn't make any sense to request balance via RPC at every second just to have the most up-to-date information. This is the danger of a generic interface. We start thinking everything fits the mold of an RPC call while having better tools at hand. |
|
great discussions in here! I just wanted to also add my overall thoughts even though the discussion progressed already. Having this as a generic RPC call requires now the app and the wallet provider to not only agree on the Nostr events as communication but also on actualy RPC methods and details. If we go down that road then where do we stop? Do we specify calls for paying an invoice, requesting an invoice, fetching the blance - but what is with doing a keysend payments or getting channel information or other LN details? So I think we should have specific NIPs for the most basic features (e.g. here sending a payment) and then have an universal RPC one for more generic things that can and should be extendible indpendently of a NIP. So in total: I think we should keep it simple and specific the complexity to make a universal LN interface is too big and will also hinder adoption. And stopping half way with only a 3-4 calls is not worth it. |
|
@bumi and I were talking and we think we should remove the New NIPs can add more active action calls to be executed by the wallet service (which would use the RPC spec) as well as more passive behaviors that a wallet service can push into the relay when it wishes to do so (which could use a non-ephemeral event type). The conclusion is that we don't have a good use case for wallet information as of now and we should not build for something we don't have good clarity for. |
|
To clarify why I think the scope of the NIP should be as limited as possible: Nostr is first and foremost a social media protocol. A rather tight integration with Lightning has developed, which is nice, but I think everyone agrees we should be careful to not add too much Lightning specific things in the NIPs. The reasoning for the NIP, as described in our original draft was to make it easier to pay zaps. We identified a clear need for this across clients. I feel there is a much less clearer need across clients to do things like show balances (my bitrefill app also doesn't show me my LN wallet balance), create invoices (we already have LN Addresses for that, like them or not), do channel management or whatever. If you want to do these things over a Nostr Relay, that's awesome and a great idea, but I do not think it should be in a NIP. It should have it's own seperate spec for people that want to build wallet or node UI's. |
I'm down with that except get_info. I want to keep that since it specifies what extensions are supported -- for now we can have that be barebones and only send the supported extensions. For the RPC spec, I think we should use an object instead of array for args for extensibility. |
But then I think |
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.
if we go with the RPC flow: maybe we can adopt wordings from the other NIP and/or follow JSON-PRC wordings.
and I actually also see this then on two levels:
the base level defines the events for the RPC calls and the second layer defines the methods and request/response data of different calls.
The base level is fixed, but the methods can be extended.
Right now it seems to me we mix this. Should commands really be defined here? How will this be expanded then?
isn't a NIP for the concept and then NIPS for individual methods better? (the RPC calls potentially don't even need to be Nost specific but are rather lightning specific and could be defined elsewhere)
47.md
Outdated
| - `NIP-47 request`: 23194 | ||
| - `NIP-47 response`: 23195 | ||
|
|
||
| Both the request and response events SHOULD only contain one `p` tag, containing the public key of the **wallet service** if this is a request, and the public key of the **client** if this is a response. |
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.
+1 I guess this would make it easier for clients to reference it?
or is this too much metadata?
47.md
Outdated
| Request: | ||
| ```jsonc | ||
| { | ||
| "cmd": "pay_invoice", // command, string |
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.
+1 and it's also more like JSONPRC then and not using yet another structure.
|
|
||
| Response: | ||
| ```jsonc | ||
| { |
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.
In JSONRPC error is an object with "code" and "message"
code's are good for apps and messages are good for humans.
if we go the generic route maybe we should allow this?
| "status": "ok", // status, "ok" | "error" | ||
| "event": "0123456789abcdef...", // event the command is in response to, string | ||
| "data": { // response data | ||
| "preimage": "0123456789abcdef..." // command-related data |
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.
YES, the preimage is needed and allows all kind of things, starting with a simple check if the that requested invoice was actually paid by comparing the payment hash to the preimage.
I also still think that NIP-57 should events published by the clients and with the preimage the same proof of payment can be provided. (making it much easier for the LNURL servers)
47.md
Outdated
| - `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 wallet does not have enough funds to cover a fee reserve or the payment amount. | ||
| - `QUOTA_EXCEEDED`: The wallet has exceeded |
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.
The wallet has exceeded what?
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.
Oops.
47.md
Outdated
| Request: | ||
| ```jsonc | ||
| { | ||
| "amount": 1000, // amount in msat, int |
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.
can we then call it msats?
it confuses so many people, most people think in sats and then run into strange errors.
I think CLN also uses msats in their params.
nip47 makes this much more doable, and the zapper key could just be the user's pubkey. |
|
I like the idea so much, I made same proposal here after clearly not searching nips properly #433. I would suggest term I would also suggest adding From my proposal: Consider also adding paginated |
|
I have made a PR to this PR: #437 I think I have incorporated all feedback that was expressed here. I hope we can reach consensus and get this merged soon. |
Co-authored-by: Semisol <[email protected]>
|
ACK Implemented NIP-47 demo here, all worked as expected. Amazing UX! |
This is a rewritten version of the original Nostr Wallet Connect proposal to be more flexible and reduce metadata leaks.