Skip to content

Conversation

@ATXMJ
Copy link

@ATXMJ ATXMJ commented Nov 14, 2025

This is a NIP for adding support for the RFC-8905 "payto:" URI scheme standard for payment target invocations

This is an updated and revised version of a nearly 3-year old PR that stalled on discussion and was eventually closed for inactivity. I believe all concerns discussed have been addressed and resolved.

@greenart7c3
Copy link
Contributor

Instead of encoding a json inside the content field it's better to use tags

{
  "pubkey": "afc93622eb4d79c0fb75e56e0c14553f7214b0a466abeba14cb38968c6755e6a",
  "kind": 0,
  "content": "",
  "tags": [
     ["payto", "bitcoin", "bc1qxq66e0t8d7ugdecwnmv58e90tpry23nc84pg9k"],
     ["payto", "unknowntype", "l7tbta5b9xze6ckkfc99uohzxd009b0r"]
  ],
  ...
}

@ATXMJ
Copy link
Author

ATXMJ commented Nov 14, 2025

Instead of encoding a json inside the content field it's better to use tags

{
  "pubkey": "afc93622eb4d79c0fb75e56e0c14553f7214b0a466abeba14cb38968c6755e6a",
  "kind": 0,
  "content": "",
  "tags": [
     ["payto", "bitcoin", "bc1qxq66e0t8d7ugdecwnmv58e90tpry23nc84pg9k"],
     ["payto", "unknowntype", "l7tbta5b9xze6ckkfc99uohzxd009b0r"]
  ],
  ...
}

@greenart7c3 thanks for the feedback!

Generally speaking, I disagree with switching to tags.

The JSON approach in content is better because:

Future more robust RFC-8905 compatibility: The payto spec supports additional fields like amount, message, receiver-name, etc. JSON can easily accommodate these in the future if desired:

{"payto": [{"type": "bitcoin", "authority": "addr", "amount": "EUR:200.0"}]}

Tags would require messy multi-value structures:

["payto", "bitcoin", "addr", "amount=EUR:200.0", "message=thanks"]

Explicit structure: JSON uses explicit key-value pairs, making data structure clear and unambiguous. Tags rely on implicit ordering of arguments, or parsing of multi-value structures, which becomes error-prone and harder to maintain as complexity grows.

Established Nostr patterns: NIP-1 defines kind 0 as JSON metadata (name, about, picture, lud16, etc.). Tags are for cross-references and querying, not complex structured profile data.

Extensibility: JSON supports the full payto specification cleanly. Tags would become unwieldy with future features and break from how other profile metadata works. Even NIP-57 uses JSON for lud16 while using tags for event relationships.

@ATXMJ ATXMJ changed the title NIP-22C9 payto: Payment Targets (RFC-8905) NIP-A3payto: Payment Targets (RFC-8905) Nov 14, 2025
@ATXMJ ATXMJ changed the title NIP-A3payto: Payment Targets (RFC-8905) NIP-A3 payto: Payment Targets (RFC-8905) Nov 14, 2025
@ATXMJ
Copy link
Author

ATXMJ commented Nov 14, 2025

Also, in response to @staab's last comment in the previous PR

in general new event kinds for signaling asset support is probably the way to go (like what has been done with cashu).

I disagree with separate event kinds for now. Basic payment targets (like lud16) belong in kind 0 metadata - they're profile contact info, not complex asset management.

Separate events create:

  • User complexity (managing multiple event types)
  • Discovery friction (extra queries needed)
  • Inconsistency (lud16 stays in kind 0)

This approach works for Cashu's rich asset signaling, but payto URIs are much simpler0.

We could reconsider separate events if NIP-A3 expands to include amounts, messages, capabilities, etc., but for basic payment invocation, kind 0 is more appropriate.

@ATXMJ
Copy link
Author

ATXMJ commented Nov 14, 2025

@staab and @vitorpamplona any further thoughts on this?

@vitorpamplona
Copy link
Collaborator

I agree with both @staab and @greenart7c3 that:

  1. Stringified JSON structures inside tags and content absolutely suck. We have been moving away from these towards tags in almost every NIP. We use multi-value structures in iMetas but a simpler positional approach is usually better/faster to parse.
  2. Separating each payment type into its own kind makes more sense since this list will inevitably grow and Kind 0 metadata is supposed to stay small because we need to load this everywhere. We are also considering moving the lighting information out to a separate kind that should only be supported by clients that support lightning. In that way, we could use one kind for each payto type.

Separating events minimizes the complexity because you only need to support the things you want and not deal with any other type of payment. Extra queries are not really happening here. You can request all supported kinds in a single request together with kind 0.

@ATXMJ
Copy link
Author

ATXMJ commented Nov 17, 2025

On point 1 (JSON):

I’m happy to align with the direction of avoiding stringified JSON. The payto structure can be expressed as tags something like ["payto", "<type>", "<authority>", ...], which keeps parsing simple and matches the positional tag patterns used elsewhere. I'll refactor the NIP to define payto via tags instead of a nested JSON structure in kind 0 content.

On point 2 (per-type kinds):

The goal of this NIP is to provide a single, generic “payment target” abstraction (payto://<type>/<authority>) instead of proliferating protocol-specific structures. Having a different kind for each payment type seems to bring us back toward per-protocol fragmentation and requires coordination every time a new payment system appears.

I completely agree that kind 0 should stay small and that clients should be able to support only the payment methods they care about. I think we can get those benefits without per-type kinds by either:

  • Option 1: defining a single payto kind with tags for multiple payment targets
  • Option 2: keeping payto as tags on profile/other events and encouraging clients to limit how much they put in kind 0

In both approaches, clients can still subscribe to only the payto kind(s) they care about and ignore unknown types, while payto remains a generic abstraction not tied to specific protocols.

@vitorpamplona
Copy link
Collaborator

vitorpamplona commented Nov 17, 2025

The goal of this NIP is to provide a single, generic “payment target” abstraction (payto:///) instead of proliferating protocol-specific structures. Having a different kind for each payment type seems to bring us back toward per-protocol fragmentation and requires coordination every time a new payment system appears.

Per protocol fragmentation will always be there because it is impossible to support all pay-to targets in a single app. It doesn't really matter if the pay-to URI is neatly organized if, in the end, the client has to load a bunch of libraries and code screens to make each payment type go through. Lightning alone already has 3 different user flows to "pay". The cost of coding each library and/or user UI flow alone is 100x the cost of loading an event kind and decoding a payment address in each protocol.

Heck, in Nostr we don't even have good implementations that do Lightning wallets and Cashu wallets at the same time. And we really know those two very well. I can tell you that the payment address URI is not the issue.

So, to me, Option 1 is a necessity. But if we do a different kind for each protocol, we don't actually need pay-to. We can just fully specialize in whatever format each protocol needs without having to do a mental model that must work for all other payment apps that a given client doesn't really care about.

@ATXMJ
Copy link
Author

ATXMJ commented Nov 17, 2025

It doesn't really matter if the pay-to URI is neatly organized if, in the end, the client has to load a bunch of libraries and code screens to make each payment type go through.

I think we have some miscommunication.

A critical point I may not have communicated clearly

This NIP is not trying to make Nostr clients implement all payment protocols. It's exactly the opposite. The goal is to standardize payment target definitions to the standardized URI scheme, and enable handling of those payments by external clients (wallet / payment apps).

It’s only standardizing how they invoke external payment handlers via payto://<type>/<authority> deep links.

In other words, the Nostr client just needs to:

  • read ["payto", "<type>", "<authority>"] tags,
  • optionally style known types, and
  • open payto://<type>/<authority> with the appropriate registered handler (app / site) in the OS / browser.

All the protocol-specific complexity you’re describing (different Lightning flows, Cashu, other systems) is handled by whatever wallet/app registers for that payto type. The aim of A3 is precisely to avoid pushing those flows into every Nostr client, and instead give them a generic, future-proof dispatch mechanism.

@ATXMJ
Copy link
Author

ATXMJ commented Nov 17, 2025

I've updated the PR to use a new kind 8905 with positional tags rather than JSON.

@vitorpamplona
Copy link
Collaborator

The aim of A3 is precisely to avoid pushing those flows into every Nostr client, and instead give them a generic, future-proof dispatch mechanism.

If that is all you want to do, why not just adding the full payto://<type>/<authority> as a tag in the new event? We can just render those in href= and that's it. There is no need to change NIP-57 or NIP-61 to support paytos, for instance.

@ATXMJ
Copy link
Author

ATXMJ commented Nov 17, 2025

I had considered that, but the reason I’m proposing ["payto", "<type>", "<authority>", ...] instead of a single opaque payto:// string is so that clients don’t have to parse URIs just to do basic UX like showing the right icon, label, or grouping by type.

With the type in a fixed tag position, a client can cheaply do:

  • “Is this type recognized?” → pick icon / styling / ordering
  • “I only care about lightning or bitcoin” → filter tags

If we store only the raw URI, every client that wants to do anything beyond a generic link has to implement URI parsing and type extraction themselves, which is exactly the kind of positional tag pattern NIPs have generally tried to standardize.

@ATXMJ
Copy link
Author

ATXMJ commented Nov 17, 2025

There is no need to change NIP-57 or NIP-61 to support paytos, for instance.

And on this, I'm not proposing any change to NIP-57 or NIP-61. Just wanted to provide an example of supporting both in this NIP.

@vitorpamplona
Copy link
Collaborator

Cool, I understood the preference for separated type/authority.

You should pick a 10xxx event number, since those are replaceables but not addressables. There is no need for the d-tag.. It's just one event per key.

@ATXMJ
Copy link
Author

ATXMJ commented Nov 18, 2025

Yeah that makes sense.

Revised the branch to use kind:10133 and removed the "d" tag requirement.

@ATXMJ
Copy link
Author

ATXMJ commented Nov 18, 2025

@vitorpamplona any other thoughts on this NIP?

@vitorpamplona
Copy link
Collaborator

Not really. Looks good to me.

If you want, you can try to reduce the amount of text. There are some duplications. The text is quite verbose. Introduction can be mostly removed. There is no need to make examples for everything. The NIP-57 integration is unecessary or it can be reduced to a single line.

@vitorpamplona
Copy link
Collaborator

vitorpamplona commented Nov 18, 2025

See if this helps:

NIP-A3

PayTo: Payment Targets (RFC-8905)

draft optional

This NIP creates a standard way to expose payment options (like Bitcoin, Nano, Venmo, etc.) to other users using the RFC-8905 (payto:) URI scheme.

Kind 10133 is used to list payment targets using payto tags with the following structure:

{
  "kind": 10133,
  "tags": [
    ["payto", "<type>", "<authority>"]
    ["payto", "bitcoin", "bc1qxq66e0t8d7ugdecwnmv58e90tpry23nc84pg9k"],
    ["payto", "nano", "nano_1dctqbmqxfppo9pswbm6kg9d4s4mbraqn8i4m7ob9gnzz91aurmuho48jx3c"],
    ["payto", "unknowntype", "l7tbta5b9xze6ckkfc99uohzxd009b0r"]
  ],
  ...
}

Where:

  • type is the network being used (e.g., "bitcoin", "lightning") in lowercase. It should consist of lowercase letters (a-z), digits (0-9), and hyphens (-). No spaces or special characters allowed.
  • authority is the address of the receiver, URL-encoded.

Clients SHOULD render each payto as a payment option and assemble a payto:// deep link URI in the format of payto://<type>/<authority> to pass the payment to other applications.

Clients implementing both NIP-A3 and NIP-57 may choose to use payto lightning entries as an alternative or complement to the lud16 field for zap functionality.

Common Payment Target Types

This is a list of recommended payment target types for clients to recognize and store icons and stylization configurations for.

Payment Target Type Long Stylization Short Stylization Symbol References
bitcoin Bitcoin BTC https://bitcoin.design/
cashme Cash App Cash App $,£ https://cash.app/press
ethereum Ethereum ETH Ξ https://ethereum.org/assets/#brand
lightning Lightning Network LBTC https://github.com/shocknet/bitcoin-lightning-logo
monero Monero XMR ɱ https://www.getmonero.org/press-kit/
nano Nano XNO Ӿ https://nano.org/en/currency
revolut Revolut Revolut N/A https://revolut.me
venmo Venmo Venmo $ https://venmo.com/pay

@ATXMJ
Copy link
Author

ATXMJ commented Nov 18, 2025

Okay, I've simplified the NIP quite a bit.

Let me know if you have any other suggestions!

@ATXMJ
Copy link
Author

ATXMJ commented Nov 20, 2025

Anything else on this?

@Silberengel
Copy link
Contributor

implemented this

@Silberengel
Copy link
Contributor

Live on https://wikistr.imwald.eu

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

4 participants