Skip to content

Conversation

@fiatjaf
Copy link
Member

@fiatjaf fiatjaf commented Dec 11, 2025

@vitorpamplona
Copy link
Collaborator

I was thinking on a crazy idea using replaceable events to reduce the amount of leaked "premium" events:

1 - Creator posts a full blog entry into the creator relay.
2 - Creator posts a created_at + 1 update with just the public part of that event and sends it to all other relays. This event also includes a "see more" tag pointing to the previous event ID and the creator relay.

Now, the other relays will not accept the old event that has the full text. They will default to the public one.

Clients will need to implement a "Load More" button that hits the creator relay and force-displays the past version of the replaceable. This will also mean that Clients using regular Nostr DBs won't save the old version in their local dbs, which will default to the public event as it should, limiting additional leaks.

@staab
Copy link
Member

staab commented Dec 11, 2025

@arkin0x @pablof7z

@arkin0x
Copy link
Contributor

arkin0x commented Dec 11, 2025

Hey guys. You may be interested in how Fanfares is doing paywalled events:

First of all, we haven't introduced any new event kinds for our paywall system. Any nostr event can be paywalled.

If the paywalled content is text, we encrypt the text and store it in the encrypted tag on the event. The content of the event serves as the preview or teaser for the event. When decrypted, the UI replaces the preview with the decrypted content (and allows the user to toggle between them as desired.)

If the paywalled content is media, we encrypt the files and store them on blossom. The event's imeta tags point to the blossom URLs and contains some extra information depending on the file type, like duration for audio files.

So whether the paywalled content is text or binary, it's just a normal event with an encrypted tag, a price tag, and zap tags defining who must get paid to unlock it.

When a user wants to publish a paywalled event, they receive the unique encryption key from our server. This is how we are able to supply the encryption key to buyers of the content.

Our server verifies zap receipts and supplies the decryption key to verified buyers of the content. The decryption happens locally in the client.

This paywall NIP is interesting for ACL for a piece of content, but I don't think we would use it for Fanfares because:

  1. This paywall NIP is interactive, meaning the content author must publish an event in response to the payment or access request. Fanfares' system is non-interactive, so the payment can be made and access given without the content author's involvement.
  2. This paywall NIP still leaves the considerably large headache of mediating access unsolved, which makes me question who would bother to build ecommerce around it.

I think that this NIP would be more appropriately named "Access Control Lists" because it has nothing to do with payment and could be used in many contexts outside of paywalls.

For what it's worth, Fanfares has managed to implement actual non-interactive zap-based paywalls without having to write any new NIPs and that's something worth considering. Our work may inform augmentations of how imeta is used, and seems to be the first to use the encrypted tag, but beyond that nothing else is needed from the protocol to make a really excellent paywall experience.

@vitorpamplona
Copy link
Collaborator

For what it's worth, Fanfares has managed to implement actual non-interactive zap-based paywalls without having to write any new NIPs and that's something worth considering.

This is not a good thing. I think you still need to create the NIP to specify how the encrypted tag works and what clients need to do about the price/zap. What encryption does it use? Where is the encryption key for files stored? Is it similar to what we do on NIP-17? What happens if there are multiple files? Once the text is decrypted what should clients do with the text? Merge with the content or replace the content? Or is it kind dependent?

Anyway, there are lots of things here for a NIP so that anyone can code not only the client but also the service.

@arkin0x
Copy link
Contributor

arkin0x commented Dec 11, 2025

Those are all great questions. I'd be happy to write a NIP. Nobody had been interested up to this point. Anyway, Fanfares is early on and I didn't see any point in defining a NIP when our POC wasn't yet complete. But it is probably 90% at this point, so I could start speccing it all out.

@vitorpamplona
Copy link
Collaborator

For now, do you just share one secret with all of the members or do you create one encrypted tag for each member?

@staab
Copy link
Member

staab commented Dec 11, 2025

Yeah, write a NIP. The whole point of nostr is network effect, and a NIP makes network effect over a feature set possible.

@arkin0x
Copy link
Contributor

arkin0x commented Dec 11, 2025

For now, do you just share one secret with all of the members or do you create one encrypted tag for each member?

Each event gets its own encryption key. Each person who buys the event receives the key in order to decrypt the event. Each buyer gets the same key given the same event.

The encrypted tag on the purchasable event is formatted like this:

["encrypted", "<encryption type>", "<optional ciphertext to replace content>"]

The key will decrypt the ciphertext.

If the event does not have ciphertext and instead has imeta tags pointing to encrypted files, the key will decrypt those files after fetching them. (The imeta has an encrypted property indicating that the fetched file is encrypted)

I hope that answers your question?

Yeah, write a NIP. The whole point of nostr is network effect, and a NIP makes network effect over a feature set possible.

It's time to write a NIP 😅

@vitorpamplona
Copy link
Collaborator

On NIP-17, we force just one encryption type to keep things easy for implementers (no random encryption schemes we have never seen). It would be worth doing the same here.

Each person who buys the event receives the key in order to decrypt the event.

How do they receive it? Do clients need to code allow them to paste/type the key into the app?

@arkin0x
Copy link
Contributor

arkin0x commented Dec 11, 2025

How do they receive it? Do clients need to code allow them to paste/type the key into the app?

The buyer makes a NIP-98 request to our server's /request-key route, and if their payment is validated, they receive the key in response.

There is a very deep discussion to be had regarding how other clients should interact with Fanfares instances. I don't know if this is the right place to dig into it. But it's a discussion I want to have. We are eager to assist other clients in integrating paywall unlocks. Just not sure how it should be done quite yet.

UPDATE: I've started writing the NIPs 📝

@pablof7z
Copy link
Member

When I implemented something like this in Highlighter I used NIP 29; the creator would publish a preview event and the full content event. The paid event with a roles tag of the NIP-29 membership roles (eg "supporter", "ultrahigh")

The NIP-29 relay enforces who gets what depending on their membership role in the relay.

This allows any client that is not aware of this paid content stuff to be able to, ie, read a paid-content 30023 long form.

@dtonon
Copy link
Contributor

dtonon commented Dec 12, 2025

@fiatjaf damn you are fast!
All good, I also like that payments are handled off-protocol.
It would be interesting to make this NIP compatible with a “pay-per-view on individual events" NIP, so that the relay could potentially implement both subscription mode and access to specific content.

@arkin0x

This paywall NIP is interactive, meaning the content author must publish an event in response to the payment or access request. Fanfares' system is non-interactive, so the payment can be made and access given without the content author's involvement

The buyer makes a NIP-98 request to our server's /request-key route, and if their payment is validated, they receive the key in response.

I don't see any major differences; in this PR, the list is updated automatically by the payment processor that interacts with the relay, so the “content author” does not need to take any direct action.

@vitorpamplona

I was thinking on a crazy idea using replaceable events to reduce the amount of leaked "premium" events:

Cool, although I'm not sure if it's a good idea to use a “side effect” as a specification.

@vitorpamplona
Copy link
Collaborator

Cool, although I'm not sure if it's a good idea to use a “side effect” as a specification.

There are no specs. Everything in nostr is a side effect. :)

@arkin0x
Copy link
Contributor

arkin0x commented Dec 12, 2025

I don't see any major differences; in this PR, the list is updated automatically by the payment processor that interacts with the relay, so the “content author” does not need to take any direct action.

As I read it, the content author must publish the kind 1163 to grant access to a new user @dtonon

@dtonon
Copy link
Contributor

dtonon commented Dec 12, 2025

@arkin0x the content author means the payment gateway acting on behalf of the author (e.g. using a FROST bunker with a limited kind scope).

@arkin0x
Copy link
Contributor

arkin0x commented Dec 12, 2025

@arkin0x the content author means the payment gateway acting on behalf of the author (e.g. using a FROST bunker with a limited kind scope).

It could mean that, depending on an individual's implementation of what is written. The spec does not include FROST.

@dtonon
Copy link
Contributor

dtonon commented Dec 12, 2025

@arkin0x FROST is not mandatory, it's just a good option. The point is that the payment gateway needs to be able to sign events on behalf of the author, or have a private communication channel with the relay that creates the events itself using the author's key.
In any case it's an automatic procedure.

@nsnjx
Copy link

nsnjx commented Dec 13, 2025

Very interesting! I'm working on a project called chuchu (still in alpha stage), which is a paid content subscription platform built on NIP 29

This NIP seems very promising. However, I have a concern that aligns with what @arkin0x mentioned earlier: if the Membership Event must be published by the content-creator, it becomes difficult to achieve a scenario where subscribers can automatically unlock content upon payment without requiring the creator's active participation.

If this NIP can address this concern, I would be interested in switching to this NIP for implementation.

@fiatjaf
Copy link
Member Author

fiatjaf commented Dec 14, 2025

You can't have your cake and eat it too. Either the thing is centralized in one provider or not, and life has tradeoffs. The limited bunker solution as @dtonon suggests is pretty good to me. Even Amber users can supply it to the payment provider as long as the payment provider has some logic to try again after some time if the bunker is unresponsive or something like that.


Well, actually you can have your cake as long as you only eat one part of it: if you are using a single relay for the premium content and that relay is also responsible for collecting the payments (or counting the zaps you receive) then that relay may start serving your content to a user that has just paid even though you haven't yet updated your list. Then later when you come back online you contact the relay and get the new data you can publish your updated list (so maybe other relays that you may also be using can get the update).

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.

8 participants