nostr:npub1jlrs53pkdfjnts29kveljul2sm0actt6n8dxrrzqcersttvcuv3qdjynqn re: labels vs event sets, I want to mark a user as a moderator of my server. This gives them special powers to delete posts from my relay. If this moderator turns bad, I want the ability to revoke them unequivocally. Labels don't guarantee that, event sets do. Also, it only matters within my own relay.


Interesting. There's no UI to display an individual repost yet. I'm not sure I can do it quickly. Thanks for working on this!


Game changer.

https://image.nostr.build/26a064c7fc887c1e571c67785fa172cbb97c19d20694474b00fc8e09b462d1a0.png


Napoleon's death mask

https://image.nostr.build/158a2ccf1da090422751bf1aa1c3bfcf62b11a83778f3d5ec69b0e55d1e51cfa.jpg


Feels like we're going against the grain. But eventually you realize all of technology up to this point has been going against the grain. So it's actually okay and even expected to go against the grain. Eventually the grain changes after enough forcing.

nostr:note1jcj4dnyc2m5kq8j0kttqmjq0l70jyj0gxxp2p0a9kc4m25zhh3yqcj4xvh


When you're trying to tell your colleague about interprocess communication but accidentally type "ICP"

https://image.nostr.build/5448ebf29551d8156019c5955245a9930a87bf55dd17396d684e9cd28e6538ea.jpg


This game looks so awesome.


Set it up similar to this repo and you can test it independently of Ditto: https://gitlab.com/soapbox-pub/gleasonator-policy


First person to create a third-party shareable #Ditto policy?

nostr:note1plysk6tfkl6vh5wgw8axvtzn9zxpc03alk8e2lv9dfdwleuv5g3skmmv9z


Nice!


I buy homophobic french fries from Chick-Fil-A and transgender vegan cheese from some vegan cheese company, so I feel these things cancel out.


Every day feels like a week. Every week feels like a month.


Sadly named "Metamon" in Japanese


#Ditto

https://image.nostr.build/def13fd5ca5b5fea491caaa119cbbd5f4c6b040adc9f0c214c8a7540d92c6f53.jpg


More info: https://docs.soapbox.pub/ditto/rate-limiting


Yes. I've been meaning to write a quick "delete by filters" script, haven't gotten around to it. The one thing you want to avoid is deleting any events signed by the DITTO_NSEC, as that is your site's configuration.


I added rate-limiting to the REST API in #Ditto. Working on WebSocket limits next.


yo yo yo


No, there is not a way to retroactively apply policies yet. But this is Nostr. You can basically just delete the database.


AntiPornPolicy for use with #Ditto or #Nostrify https://jsr.io/@gleasonator/policy/0.3.0/policies/AntiPornPolicy.ts

Simple yet effective.


Excited!


Very nice!


Yes.


>commonjs
>not typescript
>unnecessary dependencies
>doesn't actually work

This library sucks.

We ended up trying https://github.com/nbd-wtf/light-bolt11-decoder by nostr:npub180cvv07tjdrrgpa0j7j7tmnyl2yr6yr7l8j4s3evf6u64th6gkwsyjh6w6 , and of course it worked perfectly.


Ditto has a relay, so easier way is to just use nak on the Ditto relay: https://habla.news/u/alex@gleasonator.dev/nak-sync

There is no UI option for that yet.


Does anyone have a MODERN bolt11 decoder written in TypeScript?


A man ate a waffle with chocolate chips, peanut butter, and ground coffee. This is what happened to his brain.


These shapes are very interesting. #Ditto

https://image.nostr.build/00735bc175dfd53642589aa55856391593acb8a45db32ce161d66a61449342de.png


Sync user data (profile, follows, mutes, bookmarks, etc) of all users a specific person follows from one relay to another with nak:

```
nak req -k 3 -a 932614571afcbad4d17a191ee281e39eebbb41b93fac8fd87829622aeb112f4d wss://henhouse.social/relay | \
jq -r '.tags | .[] | .[1]' | while read LINE; do
    nak req -k 0 -k 3 -k 10000 -k 10001 -k 10002 -k 10003 -a $LINE wss://relay.nostr.band < /dev/null | nak event wss://ditto.pub/relay
done
```


`nak req -k 0 -k 3 -k 10002${['', ...pubkeys].join(' -a ')} wss://relay.nostr.band | nak event wss://ditto.pub/relay`


Wait kind 3s are bad? Color me shocked.


Hey Troy, sorry that happened to you. This was a fresh Ditto server, right? Not ditto.pub or gleasonator.dev. What happened is that the brand new relay didn't have your follow list, so it created a new one.

This might be helpful to restoring your old list: https://habla.news/u/alex@gleasonator.dev/1718477124783

Someone also shared this tool: https://hist.nostr.land/

We are working on fixing this issue that can happen on fresh Ditto servers. For now admins of new servers should import events from existing Nostr relays before inviting people to join their server.


nsec.app uses an obscure link-clicking flow which isn't supported by Ditto yet. The only bunker that works consistently is nak.


Do you know how long it took before ActivityPub became relevant? 10 years.

Nostr will scale better than ActivityPub in multiple different ways, I assure you it is a better foundation. But it requires one person to build a perfect library in each programming language - which nobody has done yet in JavaScript. Didn't it take you 2 months to build that in Go? This is a lot of work for one person. But I will do it.


I'm trying my best my dude. If NDK worked properly or snort/system or literally any other library I would have used it. I'm having to build everything from the ground up. Give it a little bit more time, this is just the first release, to prove that the basic idea has merit.


Sorry about that. I added a new section to the install guide to warn about this: https://docs.soapbox.pub/ditto/install#_4-sync-your-existing-data

This might also be helpful: https://habla.news/u/alex@gleasonator.dev/1718477124783


I am Alex Gleason


It is actually the ORDER BY killing performance. Without that the query is instantaneous. But obviously we need to show the most recent ones first. Trying to figure it out.


It loads very slowly, for reasons I don't understand. But it does work.

In theory performance should be good, according to benchmarks. So I don't get it.

https://image.nostr.build/1f1c33658378527b3a47dd6afc9f6d260762335f5e98543b93b76b4b30e1284f.png
https://image.nostr.build/6b99683f59d2761be0b1513dfd5e1f578756802674ab34dde523a04f51e20fad.png


Promise.all is not always the best performance. Sometimes it's better to just await in a for-loop.


Thank you!! Let me know if you need any support!


Not yet, just slightly difficult to make it portable because it needs access to your database to look up the kind 0 for each event.


#ass|#pussy|reddit

We need to train an AI on this to know what to type, lmao.


Also all of Japan and some of Korea


nostr:npub1x0r5gflnk2mn6h3c70nvnywpy2j46gzqwg6k7uw6fxswyz0md9qqnhshtn 's Ditto relay policy is hilarious. 😂

Even #fiatjaf is in there

Check: https://docs.soapbox.pub/ditto/policies

https://image.nostr.build/38483e988bdbf7905062ca1fd2c1ac48a92a238a11b8e6d8096529cd6144bf2d.png


Feel like I'm going crazy. I have a bug in Ditto that only happens exactly every hour. If I move the code around to simulate it at other times, it doesn't happen. So I can only test it every hour. It's related to Deno.cron and updating trending events.


Restore your follow list with nak: https://habla.news/u/alex@gleasonator.dev/1718477124783


Uh-oh, you just followed someone on a new Nostr client, and it wiped your entire follow list!

Let's use [nak](https://github.com/fiatjaf/nak) to check if an older version of your follow list still exists somewhere on the network:

```sh
nak req -k 3 -a YOUR_PUBKEY wss://relay.example.com
```

- Replace `YOUR_PUBKEY` with your hex pubkey (run `nak decode <npub>` to get it).
- Replace `wss://relay.example.com` with a real relay. Keep trying different relays until you find it.
- If you've set up [NosDrive](https://nosdrive.app/), make sure to check there too, as it backs up all versions of your replaceable events.

Once you've found your follow list on a relay, let's sign a new event and republish it:

```sh
nak req -k 3 -a YOUR_PUBKEY wss://relay.old.com | jq -c '{kind, content, tags}' | nak event --sec YOUR_SECKEY wss://relay.new.com
```

- Replace `YOUR_PUBKEY` with your hex pubkey.
- Replace `YOUR_SECKEY` with your hex secret key (run `nak decode <nsec>` to get it).
- Replace `wss://relay.old.com` with the relay where you found your follow list.
- Replace `wss://relay.new.com` with any relay where you want to create your new follow list.

If this succeeded, go ahead and run `history -c` to clear your terminal history, since you copied your private key into the terminal session.

Now you can copy the new follow list to any additional relays:

```sh
nak req -k 3 -a YOUR_PUBKEY wss://relay.new.com | nak event wss://relay.other.com
```

Run this any number of times, replacing `wss://relay.other.com` each time.

That's it! 🎉

If you couldn't find your event, you may be out of luck.

To learn more about syncing data with nak, read [Sync your data between relays with nak](https://habla.news/u/alex@gleasonator.dev/nak-sync).

This is a long form article, you can read it in https://habla.news/a/naddr1qvzqqqr4gupzqprpljlvcnpnw3pejvkkhrc3y6wvmd7vjuad0fg2ud3dky66gaxaqqxnzde38q6rwde3xg6rwwpn2k0mcw


Np, we will improve resolving content automatically in a future update. 😃


Check: https://docs.soapbox.pub/ditto/known-issues#some-profiles-or-posts-are-missing


Tailwind only shines when you have reusable templates or components. The old web created CSS to solve the problem in the wrong way, when they should have created components.


The difference between an API and a protocol is that a protocol calls itself a protocol.

A good protocol is documented and has multiple implementations. But an API can have that too. A protocol has to specifically by created with the purpose of there being multiple implementations to be a protocol.

ISO 3103 is a standardized method for brewing tea. Protocol.

Electrical outlets, light bulb screws, all protocols.

Snapple facts - API.


GM, I just tweaked the database settings, should fix this. #Ditto

https://image.nostr.build/4c7b29dce9e1ded84c3a299c642a437e51df1335ef2b6292e22322d68dc4dce3.png


Sounds like it was a good deal for you.

But tiny homes for rent makes no sense. The whole point of a tiny home is to buy it and own it!


"Exit night, enter Light" - true poetry


Saw a sign that said "Tiny Homes For Rent". This is truly a dystopia.


Seinfeld or Maguire?


Also, in the client-server design, normally the frontend has to authorize with the backend to access a protected resource (the database).

On Nostr the database is public. The keys are what's private.

So in Ditto, despite using a traditional client-server design, it is actually the backend which needs to authorize with the frontend to access the keys.

Soapbox (frontend) has security measures and access controls to authorize the backend to use its keys. So the frontend actually grants an access token to the backend in this design.

I believe this is what nostr:npub1jlrs53pkdfjnts29kveljul2sm0actt6n8dxrrzqcersttvcuv3qdjynqn was trying to say in our call earlier. 😂


Check: https://docs.soapbox.pub/ditto/signing

Ditto is a legacy REST API server similar to Mastodon. In this design, NIP-46 is the most viable way to sign events.

Soapbox (the frontend) has been updated to include a full NIP-46 signer implementation. When you use it, it uses NIP-07 over NIP-46!

This design is actually insane. But it also makes the most sense. The frontend (Soapbox) is not a "Nostr client" at all. It is actually a Mastodon client. The Ditto server is a hybrid Nostr client and relay, and it is actually the Ditto server which publishes your event to other relays.


Thank you!

It uses NIP-46 so it has to sign auth events as part of the flow. There's no alternative. Seems like something browser extensions could improve.


This exists: https://uptime.mostr.pub

I think the main issues with it are its database and relay pool. Once I build a new pool for Ditto I'll port it to Mostr and it should fix it.


Good question. I haven't completely ruled it out, but I want Nostr to win, and I want to spend as little time possible coding for ActivityPub. Right now I don't think it's needed since there are already multiple bridges.


A weird thing about #Ditto is that it's actually unconventional, uncharted territory to make a traditional client-server app on Nostr.


Pushed a fix. Thank you!


Yep.


People of Nostr (and the Fediverse), I have just released the first version of Ditto! 🎉

Take a look here: https://soapbox.pub/blog/announcing-ditto/

nostr:note15uud6xz30e8fe3vsvh95xcklwk2azggfawlankedd0uuqh0ug7xqvmuy7y


Hood classic.


Yes, I prefer Freedom software. 😃


The zaps counter in #Ditto is looking good! nostr:npub1gujeqakgt7fyp6zjggxhyy7ft623qtcaay5lkc8n8gkry4cvnrzqd3f67z

https://image.nostr.build/61b506a1e31e479568316c93f3e0e15c6861107af404aaf92c4631338b0cdb1d.png


Fresh peppercorns. Kind of mind blowing honestly.

https://image.nostr.build/8b623b39dd26722c457ccd6b31a8b6500130272d650c93c0538de95d02a3a2c1.jpg


That looks great!!


Use this to store events in SQLite/Postgres: https://nostrify.dev/store/sql

Then glue it to a web server with Hono: https://hono.dev/


No, it uses NIP-46. You need a remote signer like nak/Amber/nsecBunker.


You can use Buffer's Mastodon integration to post on Nostr through #Ditto. 😂

https://image.nostr.build/72bbae939d35deadc2a22809b2d98277bf31a358b3fa52ad9472ac4d74782e31.png


Ditto: https://ditto.pub


Zaps modal


Look at what this man built:

nostr:note160dfnl45h78trwt2ggdevaerm78m2swk095tr604uzz5zt8l4hsq5u22g5


Y'all don't understand this level of swag.

https://image.nostr.build/9bd1c7b02751a691f7813f3009fb21104e4486e947e00ac38e607a6fb4624f0b.jpg


>nostr events are fully portable
>ditto stores the events in postgres
>multiple ditto servers can use the same postgres instance 🤯
>but they lose streaming support
>LISTEN/NOTIFY ??

https://www.cybertec-postgresql.com/en/listen-notify-automatic-client-notification-in-postgresql/


Check out NDatabase: https://gitlab.com/soapbox-pub/nostrify/-/blob/main/src/NDatabase.ts

Your way with triggers will be more performant on SQLite. I know this because I benchmarked various scenarios extensively. But my way is more flexible and thoroughly documented/tested/benchmarked, and works perfectly in both SQLite and Postgres.

Check: https://nostrify.dev/store/sql#custom-tag-indexes

The declarative tag indexer is a wrapper I built around NDatabase, available here: https://gitlab.com/soapbox-pub/ditto/-/blob/main/src/storages/EventsDB.ts


What tags do you need indexed?


He is a beautiful man with a fantastic mustache. Not as good as mine, but pretty close.


Yes, check out gleasonator.dev


Probably my greatest Nostr relay innovation. Extremely customizable tag indexer.

https://image.nostr.build/1c497d4993eb9aedca3a245ed8f774f38caacc7ae23e06005d574b9a9c8e36f0.png


There's a small problem with pagination due to Mastodon not implementing its own API correctly. 😃


#Ditto + Mastodon Android works! (mostly)

https://image.nostr.build/ee3ae7e84fedafad1fedbd8c9bb87386ff49fb815563928385e37e21bafae57d.jpg
https://image.nostr.build/a1e6127ad052cf5c6e4cbf178383380d5f26cfcca437083dacc45c83ee855a94.jpg


nostr:npub1jlrs53pkdfjnts29kveljul2sm0actt6n8dxrrzqcersttvcuv3qdjynqn Very nice!


In 2016 I jumped on the stage of Nathan's Hot Dog eating contest and unfurled a banner that said "Animal Liberation Now".

It blows my mind to learn that today Joey Chestnut, the all-time hot dog eating champion, has quit competitive eating to endorse a vegan hot dog brand. 😂 😂 😂

https://www.goodmorningamerica.com/news/story/joey-chestnut-competing-nathans-hot-dog-eating-contest-111030108

https://image.nostr.build/593cac9c32ba6ea6cc3476469422739180f085a0a93d97fd150ffd60e2454ce6.jpg


I fixed a small problem related to Websockets/Nostr in Deno and they acknowledged it in their blog post. That's very nice.

https://deno.com/blog/v1.44#websocket-timeout

https://image.nostr.build/bad381d7ef4a2f767f8ee6b1cb8124428a96d65ede4ba8683ccc7f1fcd0a32d4.png


Websockets are truly horrible to work with. But we need them anyway to support streaming. So HTTP isn't an alternative. It's an additional option, adding complexity.

Websockets can be mastered though. So we just need to build perfect libraries in each language. In theory this work only needs to be done once, then it will be easy for all future developers.


It fully works. You just need a remote signer to use it.


Ah, the two genders. Clients and relays.


Viewing Nostr through a Mastodon terminal app through #Ditto

https://image.nostr.build/1a7a65f96db73e0909363b79c57b54166f75f0aff64aa8b19b2137baffb4f6e5.png


The fact !(A || B) is the same as (!A && !B) drives me crazy.


Another #Nostrify SQL performance win: avoid "order by" in ids queries (it's not needed): https://gitlab.com/soapbox-pub/nostrify/-/merge_requests/62/diffs


Almost ready to launch this thing

https://image.nostr.build/590875c013b18539fdb3c7da8c7f3120395050ab746c18d2901300dee91919e8.png


Good, streaming notifications work now


yolo


It's another thing entirely.


test


Never seen that one, cool!


Brave

https://image.nostr.build/3ded886210eee3e1a17f54c04190aee19c2e8c608c1a5e3d3270885b25e7b4a9.png


Winning? Success is the best revenge.


They will find any way they can to discredit what we're building - because it's a threat.


I really thought he was Satoshi Nakamoto.


I keep seeing videos of people going deep in a cave until they get totally stuck and die. I don't mean to be insensitive, but all you have to do is don't do that.


I updated the trending pubkey algorithm. Now it's based on recent interactions, not follows. It's WAY better.

https://image.nostr.build/cd0172583851bcdc8d9f71a9630f75734fbb9ab00c036bf2e03cbf53543beb3c.png


I don't see why not. Although there's a small display issue with the hashtag.

https://image.nostr.build/32297fe758d44b6444c2f60bf1a7a5d6c7e3f2ca9eef91d0e2aad70cea278822.png


That's so awesome!!


What's also cool is that for all these, the algorithm works the same way. With a few tweaks like treating "e" and "q" as the same. It just tracks popular tag values in recent events. So we could actually track any tags with this.

https://image.nostr.build/8a0f0363a9839c4ba29486bca0dc3a75df145389022415da1ec1d055d0b4b4f5.png
https://image.nostr.build/f8898df3654fda5affc398ed29adf5f744ed8ac95e0a812dd65a9f4c367d0497.png


Oh right, I almost forgot, that works the same way. It has l=#e

Check: https://gleasonator.dev/search


Check: https://soapbox.pub/ditto/

I'm running mine on gleasonator.dev and we just created a ditto.pub as well. Will be officially releasing very soon!


This trending hashtag algorithm in #Ditto works by querying the database every hour and publishing a Label event with the top hashtags.

Then to display it, we get the most recent Label, then get a 7-day history for each of those tags.

Check trends with:

nak req -k 1985 -t L=pub.ditto.trends -t l="#t" -a db0e60d10b9555a39050c258d460c5c461f6d18f467aa9f62de1a728b8a891a4 wss://gleasonator.dev/relay

Change #t to #p for trending users, or #r for trending links.

https://image.nostr.build/4145661101e8f200b35a249ba9a3acb175d71fe424fd2808649ac0636a4d0a25.png
https://image.nostr.build/3b65fdd0e09f32f04b9338daee1c4a003e80086c311ac8e636b8de3485de163b.png


The issue is not push notifications. The tab is open. The issue is error handling. I can only reset the state of the app by creating a new account. Don't you see how the complexity is a problem? It's getting crushed under its own weight.


It should not be in the NIP. That's crazy. It barely works as it is and we've loading new features on top of it. As a developer it's frustrating. I don't want to implement this special flow just for an app that only works half the time.


Why is nak the only NIP-46 app that actually works? Runner-up is Amber which mostly works but still a bit finicky. nsecBunker requires payment and then the app is unusable. nsec.app uses an obscure link-clicking flow then only works half the time. nak works perfectly every time, but it's a command line tool. We need to make Amber great.


And that person's name was: Alex ?


How do I commission a trap remix? https://www.youtube.com/watch?v=SKUs0dtrMy8


Art imitates life more than life imitates art.


It's because this is an alien software that uses HTTP. If you have an upgraded Alby you can scroll to the bottom and change it in the prompt.


Log out and back in


It's crazy how we all just forgot the pandemic. https://www.youtube.com/watch?v=1Rx_p3NW7gQ


nostr:npub108pv4cg5ag52nq082kd5leu9ffrn2gdg6g4xdwatn73y36uzplmq9uyev6 test


nostr:npub180cvv07tjdrrgpa0j7j7tmnyl2yr6yr7l8j4s3evf6u64th6gkwsyjh6w6 test


nostr:npub1xtscya34g58tk0z605fvr788k263gsu6cy9x0mhnm87echrgufzsevkk5s what ever happened to your scrobbles in Damus? I thought it was a nip but I can't find it.


yolo

nostr:note12jvla2nvmee42mr3f9zwvuswlm05ey9ze5kz2w96k4cta988x25sp7x02l


What client did you post this from?


Shrek homies say "drek"

Shrek haters close your phone.

https://image.nostr.build/0b77992e126b2f35ded7f90cb21e4ed3879f43d76c0d55dd2bbef1290a625a22.jpg


It's a threat to what they're building, by people who chose to take a different route than the one they were already traveling. I'm not surprised they're salty about that. ActivityPub itself is basically RSS.

Just give it time. People will come around when they realize it's here to stay, and we give them an easy path to join us.


It's because they're building a new podcasting platform on ActivityPub.


I'm posting from gleasonator.dev right now, but still working out the kinks.

Give nostr:npub10qdp2fc9ta6vraczxrcs8prqnv69fru2k6s2dj48gqjcylulmtjsg9arpj a follow for updates!


Finally somebody gets it.


Zap progress on #Ditto ! (don't worry, we'll make it beautiful later)

nostr:note1qmqdualjezamcjun23l4d9xw7529m7fee6hklgtnhack2fwznxysuzuuyz


Agreed on all fronts, but people will never stop asking about it.


Should I add the IPFS Uploader to #Nostrify? The code is already written and it works. We have Blossom and NostrBuild already.

https://image.nostr.build/bf08fc42bf3e4ba3363900576df083d0b13138442e88d4dd19205fa285b32ead.png


You are insane for everything you managed to cram into Amethyst. Good job. 👏


Woooo! 🎉 Congrats!


Yes exactly! 💯 I knew you would be into this. My Ditto project is all about domains. Hoping we can build some awesome communities this way. More examples:

https://image.nostr.build/2037891933d23476e06acd1f82436bec554a403e22e79b40edc45bc439bc9886.png
https://image.nostr.build/9775c51bc7bc0b567bea6da45dbc71784cecb90d36e99305b20bf82c04e5655b.png


People say it works: https://github.com/denoland/deno/issues/8012#issuecomment-1550875329

It is not officially supported. But is Node? 🤔

Deno is much less pain. But I assume if you use an OS even more obscure than Linux you must enjoy the pain. So carry on. 😄


Which is?


Switch to Deno.


nostr:npub18ams6ewn5aj2n3wt2qawzglx9mr4nzksxhvrdc4gzrecw7n5tvjqctp424 Have you seen this? 👀

https://image.nostr.build/95ebc0f0a5f8b5297dd9851c6e0856da8692a8c286c5e34dd705eefd61cb0127.png


It's possible to use nostr.build and Blossom uploaders interchangeably: https://nostrify.dev/upload/#implementations


yo yo yo yo yo yo yo


Paper Mario transgender representation


GM my shit is all fucked


Nice


Danger Mario is the best.


Ideas are cheap. Code is hard. There's no excuse for a tech leader to not also be a coder.


I believe that products should be developer-driven. However, you also need a visionary to create a cohesive vision. Therefore the visionary needs to be a developer.


What the fuck is this? 1705215483996


Are you testing the relay tester right now? Because my relay keeps crashing. 😂


Check: https://docs.soapbox.pub/mostr/troubleshooting/


test


Relatable

https://video.nostr.build/d85928c449a2b752b2848068a3fab8be54afe372582dc11f768369a7f0d71e11.mp4


I'm truly so grateful to have the most amazing, smartest wife of all time. Love you nostr:npub1jvnpg4c6ljadf5t6ry0w9q0rnm4mksde87kglkrc993z46c39axsgq89sc  💜💜💜


The prose outways the cons.


This website is basically usable


It's true. The client needs to be resilient and end the connection itself when the results have been fulfilled. Knowing the intrinsic limit can help that.


Nostr has an optimization problem. Events can exist anywhere on the network, and clients don't know when to stop looking.

This is the same problem the **outbox model** aims to solve, but you can do more than just route authors to specific relays. You can make certain judgements about filters themselves.

On Nostr, different filters have different fulfillment criteria.

For example, take the `ids` filter:

```json
{ "ids": ["1", "2", "3"] }
```

Since events on Nostr are immutable, and IDs are a hash of the event's content, this filter is _inherently_ fulfilled when we have gathered (and verified) all of the events with these IDs.

So, the above filter can be optimized by adding a `limit` equal to the number of IDs:

```json
{ "ids": ["1", "2", "3"], "limit": 3 }
```

The limit is a hint that both clients and relays can use to know when to stop searching. For example, a client who sends this REQ to five relays can short-circuit as soon as the 3 events have been found from _any_ relays, without having to wait for all five relays to EOSE.

## Replaceable events by author

A less obvious optimization concerns filters for replaceable events by author, like this:

```json
{ "kinds": [0], "authors": ["alex", "fiatjaf"] }
```

Kind 0 is a replaceable event, meaning relays should only serve one event of that kind for each author. As a result, the intrinsic limit of the filter is equal to the number of _authors_ in the filter:

```json
{ "kinds": [0], "authors": ["alex", "fiatjaf"], "limit": 2 }
```

When filtering by multiple replaceable kinds, the intrinsic limit is the number of authors multipied by the number of kinds:

```json
{ "kinds": [0, 3], "authors": ["alex", "fiatjaf"], "limit": 4 }
```

Adding a single non-replaceable kind to filter makes us lose knowledge about its limit, making the filter potentially infinite again:

```json
{ "kinds": [0, 3, 1], "authors": ["alex", "fiatjaf"] }
```

## Empty filters

A final optimization allows clients to avoid sending filters to a relay at all, because the filter's intrinsic limit is **0**. Those filters are listed below.

A filter with an empty kinds array:

```json
{ "kinds": [] }
```

A filter with an empty authors array:

```json
{ "authors": [] }
```

A filter with an empty ids array:

```json
{ "ids": [] }
```

Any other filter that contains contains an empty `kinds`, `authors` or `ids` always has an instrinsic limit of 0:

```json
{ "kinds": [], "authors": ["alex", "fiatjaf"] }
```

(It doesn't matter that you're searching for authors here. Since no kinds can match, the filter has an intrinsic limit of 0.)

All of these filters can be optimized by adding `"limit": 0` to them.

Clients can optimize empty filters by not sending them to relays at all, and relays can optimize them by not querying their database. In both cases, an empty result set is returned.

## nostr-tools

A function to calculate the intrinsic limit of a filter has been [added to nostr-tools](https://github.com/nbd-wtf/nostr-tools/pull/340). It looks like this:

```ts
/** Calculate the intrinsic limit of a filter. This function may return `Infinity`. */
function getFilterLimit(filter: Filter): number {
  if (filter.ids && !filter.ids.length) return 0
  if (filter.kinds && !filter.kinds.length) return 0
  if (filter.authors && !filter.authors.length) return 0

  return Math.min(
    Math.max(0, filter.limit ?? Infinity),
    filter.ids?.length ?? Infinity,
    filter.authors?.length && filter.kinds?.every(kind => isReplaceableKind(kind))
      ? filter.authors.length * filter.kinds.length
      : Infinity,
  )
}
```

To use it, you can import it from `nostr-tools`:

```ts
import { getFilterLimit } from 'nostr-tools';
```

Then before you query a relay, you can modify the filters to add `limit`s, and remove any empty filters:

```ts
/** Query a relay using Nostr filters. */
function query(filters: NostrFilter[]): Promise<NostrEvent[]> {
  filters = optimizeFilters(filters);
  return relay.query(filters); // call the relay
}

/** Add a `limit` to each filter if possible, and remove filters that can't produce any events. */
function optimizeFilters(filters: NostrFilter[]): NostrFilter[] {
  return filters.reduce<NostrFilter[]>((acc, filter) => {
    const limit = getFilterLimit(filter);
    if (limit > 0) {
      acc.push(limit === Infinity ? filter : { ...filter, limit });
    }
    return acc;
  }, []);
}
```

## Combined with the Outbox Model

While `ids` filters are inherently fulfilled when the intrinsic limit is reached, replaceable events by authors can have multiple versions of the events on different relays. So adding a limit does not completely solve the problem on its own.

But combined with an Outbox approach, where specific relays are selected for specific authors, we should be able to stop querying when the intrinsic limit is reached, with reasonable confidence that we have the latest version (or one of the latest versions) of the events. This combined approach helps us resolve content faster and more accurately.

Improve performance of Nostr clients and relays by analyzing filters and limiting results.

This is a long form article, you can read it in https://habla.news/a/naddr1qvzqqqr4gupzqprpljlvcnpnw3pejvkkhrc3y6wvmd7vjuad0fg2ud3dky66gaxaqqfkummnw3ez6enfd36x2unn94kxjmtfwsqrkey5


Can't stop thinking about Blossom.


I describe it as a Nostr "server" instead of client or relay. It's both.


Ditto implements the Mastodon API so you can use Mastodon apps with it. It's also a Nostr relay.


It remembers me now when the page loads, that's nice


yo


[nak](https://github.com/fiatjaf/nak) is an awesome command-line tool written in Rust (jk, written in Go) for doing Nostr stuff. I use it on a daily basis to talk to relays and gather events.

Using nak, you can sync your data between one relay and another like this:

```sh
nak req -a <pubkey> <relay-1> | nak event <relay-2>
```

For example:

```sh
nak req -a 0461fcbecc4c3374439932d6b8f11269ccdb7cc973ad7a50ae362db135a474dd wss://gleasonator.dev/relay | nak event wss://slime.church/relay
```

This downloads all data by the **author** (`-a`) from relay 1, and pipes it into relay 2.

Most relays only return about 100 events at a time, though. To sync _everything_ you will need to implement some sort of pagination with `since` and `until` filters.

Most commonly I just need to sync my profile data to a new relay, which can be done with the `-k` (kind) flag. You can pass multiple `-k` flags, eg:

```sh
nak req -k 0 -k 3 -k 10000 -k 10001 -k 10002 -k 10003 -a 0461fcbecc4c3374439932d6b8f11269ccdb7cc973ad7a50ae362db135a474dd wss://gleasonator.dev/relay | nak event wss://slime.church/relay
```

This will migrate your profile, your follow list, your relay list, your mute list, pinned posts, and your bookmarks to the new relay.

If you find yourself doing this often, you can drop a little alias in your bashrc/zshrc:

```sh
alias nsync="nak req -k 0 -k 3 -k 10000 -k 10001 -k 10002 -k 10003 -a 0461fcbecc4c3374439932d6b8f11269ccdb7cc973ad7a50ae362db135a474dd wss://gleasonator.dev/relay | nak event"
```

Then you'll just `nsync <relay>` to copy data from your main relay to another relay!

This is a long form article, you can read it in https://habla.news/a/naddr1qvzqqqr4gupzqprpljlvcnpnw3pejvkkhrc3y6wvmd7vjuad0fg2ud3dky66gaxaqqyxuctt94ehjmnrfqzdr3


My Little Pony Hitler


nostr:npub1w4uswmv6lu9yel005l3qgheysmr7tk9uvwluddznju3nuxalevvs2d0jr5 nostr:npub1w4uswmv6lu9yel005l3qgheysmr7tk9uvwluddznju3nuxalevvs2d0jr5 nostr:npub1useke4f9maul5nf67dj0m9sq6jcsmnjzzk4ycvldwl4qss35fvgqjdk5ks hello world


hello


Yes, it reminds me of Truth Social.


GM Google drive


yolo


I want a Game Boy Nostr signer.


I beat Hollow Knight in Steel Soul mode (without dying)

https://steamcommunity.com/id/gleasonator/stats/367520/achievements/


kim jong un trump mcdonalds


hello world


Should I click this button? Seems important.


The Big Lebowski is not a good movie.


GM, I stringified nip46 params


Supporting Palestine is the stupidest thing ever. Or Israel.


The term "white paper" is so stupid. All paper is white.


Can't sleep... looking through Osama Bin Laden's hard drive.


GM fellows


hi


Just realized Blue Oyster Cult was talking about the mushrooms.


Sounds can be produced at any frequency. Sheet music is just a protocol.


I dreamed about Japan.


It's raining. Rain is good.


Hello


yolo


Why did you call yourself a name that already exists? Have you never heard of the Fediverse before? Or are you trying to intentionally cause confusion?