Skip to content

Conversation

@vitorpamplona
Copy link
Collaborator

@vitorpamplona vitorpamplona commented Sep 4, 2023

Uses unbound lists to create events that define the relationship between two pubkeys in a public or private manner.

Read here

@jb55
Copy link
Contributor

jb55 commented Sep 4, 2023

cc @Semisol

@alexgleason
Copy link
Member

Will this allow for Alex Gleason set their relationship status to "it's complicated" with X?

@arthurfranca
Copy link
Contributor

Previous discussion regarding contact list at #349
tldr: see "kind X follow (by @vitorpamplona)" section here

@fiatjaf
Copy link
Member

fiatjaf commented Sep 7, 2023

@alexgleason I think NIP-32 may be the best way to declare public relationship statuses.

@vitorpamplona
Copy link
Collaborator Author

There are so many jokes here that I don't know if this proposal has any legs 😅

@arthurfranca
Copy link
Contributor

Most "Relationship Status" lists will have few items so NIP-51 would be enough.
Hopefully I expect my unbounded list PR #784 to be at a good enough state to suggest holding some ever growing data like subscriber list.

@vitorpamplona Please take a look at this example I just added there:

nips/61.md

Lines 53 to 87 in 8ae2b5d

```js
{
"kind": 33118,
"tags": [
["d", "random6467362966154779"]
// this is enough to mark it as a "subscribers" unbounded list
["n", "subscribers"],
// This also adds this event to other unbounded list (e.g.: meant
// to hold all pubkeys who can access the
// 5c83da77af1dec6d7289834998ad7aafbd9e2191396d75ec3cc27f5a77226f36 event)
// made of the "subscribers" unbounded list events
// and other events such as NIP-51 Categorized People lists
["n", "private:5c83da77af1dec6d7289834998ad7aafbd9e2191396d75ec3cc27f5a77226f36"],
],
"content": "<NIP-44 encrypted Stringified TAG-List([
["p", "91cf9..4e5ca", "wss://alicerelay.com/"]
])>",
...other fields
}
```
```js
{
"kind": 30000,
"tags": [
["d", "close_friends"]
["n", "private:5c83da77af1dec6d7289834998ad7aafbd9e2191396d75ec3cc27f5a77226f36"],
],
"content": "<NIP-44 encrypted Stringified TAG-List([
["p", "14aeb..8dad4", "wss://bobrelay.com/nostr"],
["p", "612ae..e610f", "ws://carolrelay.com/ws"]
])>",
...other fields
}
```

@vitorpamplona
Copy link
Collaborator Author

vitorpamplona commented Sep 21, 2023

So, the goal for Relationship Status is that most of us have over 10,000 contacts (not friends, not follows, just contacts). And a list with 10,000 people is quite a heavy list to be updated all the time. That was the reason to start this.

Like, an email client, for instance, can have a status group for every person that sent you an e-mail. Instead of updating the list in every new e-mail and blasting all relays with it, the client just adds a Relationship Status event.

@arthurfranca
Copy link
Contributor

arthurfranca commented Sep 21, 2023

So, with my unbounded list proposal (basically the n tag and the 33118 kind for "people") it could be like this:

{
  "kind": 33118,
  "tags": [
    ["d", "random6467362966154779"]
    ["n", "contacts"]
  ],
  "content": "<NIP-44 encrypted Stringified TAG-List([
    ["p", "91cf9..4e5ca", "wss://alicerelay.com/", "Alice"],
    ["status", `<Known Face, Contact, Following, Coworker,
      Friend, Partner, Family, Extended family, Trusted,
      Competitor, Traitor, Used to Know, Scammer, NSFW,
      Unkown, etc>`],
    ["summary", "<summary of the relationship>"],
    ["email", "bla@ble.com"]
  ])>",
  ...other fields
}

The difference is that you can later add the same entry simultaneously to another unbounded list editing it and adding a new n tag.

Want all contacts, search by the n tag and author.

Edit: the event is supposed to hold just one p tag (encrypted) so you add other stuff there like the email adress tag, or a separate summary tag. Edited the above example.

@arthurfranca
Copy link
Contributor

@vitorpamplona Added you as author and also the "contacts" example there at #784
Please check it out.

@vitorpamplona
Copy link
Collaborator Author

The difference is that you can later add the same entry simultaneously to another unbounded list editing it and adding a new n tag.

I am confused. What does it do? Why would I add the entry to another unbounded list?

@vitorpamplona
Copy link
Collaborator Author

vitorpamplona commented Sep 21, 2023

I would separate the status example from a people list example. Otherwise, you have to link each status tag with each p tag (since you can have multiple p-tags in kind: 33118

So, an unbound list allows a client to search for multiple n tags in the same kind, while here you would get the "unbound" list by just getting everything with kind:30382.

You could turn the status into the unbound event's n tag. Then you can group people into one or more unbound status lists while still being able to get a single person's status by filtering by kind:30382, p tag and pubkey.

Wouldn't that be the right way to do this?

@arthurfranca
Copy link
Contributor

arthurfranca commented Sep 21, 2023

you can have multiple p-tags in kind: 33118

A kind 33118 event was meant to have a single p tag (I have recently edited the description pointing that out), because an unbounded people list (with a specific n tag) is made of one to many of these events.
There is another example there with two p tags but it is the NIP-51 kind 30000 with an n tag.

You could turn the status into the unbound event's n tag... to get a single person's status by filtering by kind:30382, p tag and pubkey.

Hmm got it you want to have kind 30382 with a p tag unencrypted (to able to use on filter). I encrypted the n tag content
to hide the info there (edit: so you would have to encrypt "known_face" before using on filter).

{
  "kind": 30382, // can fetch all contacts
  "tags": [
    ["d", "91cf9..4e5ca"]
    ["n", <NIP-44 encrypted("known_face")>], // can fetch all "known_face"s but no one knows the status
    ["relay", "<recommended relay>"],
  ],
  "content": "<NIP-44 encrypted Stringified TAG-List([
    ["nickname", "<My buddy>"],
    ["summary", "<summary of the relationship>"],
    ["email", "bla@ble.com"]
  ])>",
  ...other fields
}

Is this it?

@arthurfranca
Copy link
Contributor

arthurfranca commented Sep 21, 2023

If the above ["n", <NIP-44 encrypted("known_face")>] is a good idea, maybe you should standardize a NIP-51 list
holding all used n statuses?

I encrypted it because someone may get mad if it discovers his friend labeled him as a "scammer" or something worse =o

@vitorpamplona
Copy link
Collaborator Author

ohh interesting.. i didn't know the 33118 was supposed to be for just one person. I thought it would be a list of lists of people. Which is probably useful in some use cases.

["n", <NIP-44 encrypted("known_face")>]

Maybe using status for n is not a good idea. I am not sure if the encryption is deterministic to make sure all known_faces map to the same base64.

@arthurfranca
Copy link
Contributor

Maybe using status for n is not a good idea. I am not sure if the encryption is deterministic to make sure all known_faces map to the same base64.

Just tested it. It is not deterministic =(

nip44.encrypt(hexToBytes(sk1), 'scammer') // 'AU9OCx1Rn7PAA5TxDqgi5Up7bK+cNAgI2bU5/BhQmg0='
nip44.encrypt(hexToBytes(sk1), 'scammer') // 'AfCaDG/6jzFvQKEAVkaKzlgKIsPddNWXaynDPPNb/64='

@arthurfranca
Copy link
Contributor

maybe you should standardize a NIP-51 list holding all used n statuses?

If searching by the status at n tag was a must have feature, it could still be done if such NIP-51 list (holding all used n statuses) had tags like this:

[
  ["status", "<status 1>", "random string 1"],
  ["status", "Big Time Scammer", "6064460175057025"]
]

Then the n tag would be "6064460175057025"

@vitorpamplona
Copy link
Collaborator Author

@arthurfranca I am back to this, changed it to a private status (hash of the key as d-tag) and changed it to your unbound list idea.

I will be using this to document a list of Patients a doctor is taking care of. People can count how many patients a doctor has, but the relationship between them should be fully private.

@Silberengel
Copy link
Contributor

Then why isn't it merged, if it's already in active use by multiple clients? Have to dig through the PRs, to find it.

@vitorpamplona
Copy link
Collaborator Author

Then why isn't it merged, if it's already in active use by multiple clients?

Merging things too early in a core proposal (it could replace contact lists) like this one is bad.

I am ok with waiting for more people to code and be convinced that this is a good approach or not.

@Silberengel
Copy link
Contributor

Hmm. Just means that protocol changes in the repo are too difficult and slow, so that the development of the protocol always happens elsewhere, and this repo is now just an archive of old versions.

@vitorpamplona
Copy link
Collaborator Author

vitorpamplona commented Dec 16, 2024

Just means that protocol changes in the repo are too difficult and slow

Protocol changes should be slow. New usage of the protocol can be much faster. Most NIPs are just new usages.

the development of the protocol always happens elsewhere

As it should. Proposals should be developed, tested and show some proven usage before the specs are merged. Otherwise, we would have had 1000s of NIPs already and 90% would be unused. Lots of noise, little signal.

repo is now just an archive of old versions.

This repo does not direct where Nostr is going. It only reflects what people are actually doing and using. PRs are just propositions of where apps want to go to allow other devs to react to any blatant issue when encoding things into Nostr.

@Silberengel
Copy link
Contributor

Okay, but then, what's the advantage of keeping the spec in the repo, versus just having a NUD in the wiki and listing the corresponding event kind in the repo ReadMe? Everything in here is outdated.

@vitorpamplona
Copy link
Collaborator Author

what's the advantage of keeping the spec in the repo, versus just having a NUD in the wiki and listing the corresponding event kind in the repo ReadMe

There is no significant advantage for the NIP. It's just additional exposure. In fact, many people would say NUDs are a better system for specs than a centralized repo run by Microsoft.

Everything in here is outdated.

Outdated means obsolete. Which is not the case. This repo just doesn't have all the crap people are testing, kick-starting, etc. It only has the things that have shown they are useful.

Think about this repo as an interoperability layer. If multiple clients are trying to be interoperable, this is a good spot to come together. If a client is not interested in getting others to use their stuff, they don't need to be here at all. In fact, many couldn't care less about interoperability.

@Silberengel
Copy link
Contributor

We low-key hate GitHub, but Nostr development has kind of forced us to use it. We were usually using other git servers, before.

Copy link
Contributor

@jb55 jb55 left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

its a good idea

@vitorpamplona vitorpamplona marked this pull request as ready for review May 3, 2025 14:12
@arthurfranca
Copy link
Contributor

Recently was thinking that maybe the n tag is so similar to the l tag from NIP-32 that this PR should change to using l and omit the namespace part (L tag) considering it optional.

@arthurfranca
Copy link
Contributor

@staab @vitorpamplona keep n or change to l tag?

@staab
Copy link
Member

staab commented May 9, 2025

I would use l

@vitorpamplona
Copy link
Collaborator Author

vitorpamplona commented May 9, 2025

We had that discussion before. Just because they have the same format doesn't mean they are the same.

  1. I don't think we need kind:10008s name overrides for NIP-32 labels. Mixing the two tags by declaring here that we are using NIP-32 labels could just confuse everyone that kind 10008's overrides apply to all NIP-32 labels, which is not the case.

  2. NIP-32 Labels are designed to have some notion of meaning that is larger than a pubkey. That's why it focuses on ontologies so much. Each user may have his/her own interpretation of the ontology but it is beneficial to NIP-32 if labels remain constrained to the classification/ontology work, even on optional name spaces. n tags are completely unbound to anything. Only their public key knows their true meaning. Users can and will lie on those names on purpose.

The semantical difference between a hashtag, a label, and a set is big enough to justify their separate existences. Even though they all use exactly the same syntax and largely intersect with one another.

@arthurfranca
Copy link
Contributor

My doubt is if its worth it having twothree (t tag too, good catch) global tags (global as in a tag that has the same meaning across all events) to make that distinction as 1) t is a loose label usually tied to #topic in .content ; 2) l is a formal label linked to a namespace though there's ugc (user generated content) that makes it close to t and even n; 3) n is a label that categorizes people or put things on "folders" or set a status to open/closed (@alexgleason).

Are they really that different? I know that sometimes one wants to have namespaced labels together with ugc, like for example an app wants you to pick a label for some event from their fixed list of labels and you may also add some hashtags to label it freely.

On the previous example, what we want is these to not clash when filtering at relays so today that hypothetical app would pick l (or n) and t.

As food for thought, what if we picked the t (oldest) and merged all the concepts. I will add that it is also nice to know when any hashtag is present (similat to nip32 L tag).

Example of someone labeling a website at Yahoo:

{
  // ...other event fields
  tags: [
    // T is "tag presence" plus optional namescape
    // Good if we want all kind:xyz events with "hashtags"
    // or all events labeled with the namespace my app cares about
    ["T", "t"],
    ["T", "t:com.yahoo"],
    ["t", "free-stuff" /* normalized */, "Free stuff" /* to display it as typed by user */],
    ["t", "com.yahoo:app", "App" /* to display it as intended by Yahoo */],
    ["t", "com.yahoo:ar", "AR"],
    ["t", "com.yahoo:vr", "VR"],
  ]
}

@vitorpamplona
Copy link
Collaborator Author

global tags

There is no such thing as a "global tag" on Nostr. Each NIP redefines every tag, both syntactically and semantically, to their own liking. Most of the redefinitions are syntatically backwards-compatible but semantically quite different. In code, you can never expect a global tag approach to last.

merged all the concepts

There is no point in merging concepts. What's to gain from it? You lose all the beautiful specialization that each of them have for the sake of what? One less character to use on your source code? One less if (if "t" -> if "t" or "l")?

Generalizations are the enemy of interoperability.

So, no I don't think we should merge t and l until we find some massive gains for the user.

@jb55
Copy link
Contributor

jb55 commented Aug 18, 2025

We are about to implement this at damus for "favouriting users"

can we call these Nostr Contact Cards instead?

@vitorpamplona
Copy link
Collaborator Author

can we call these Nostr Contact Cards instead?

Yeah, we can change

@vitorpamplona
Copy link
Collaborator Author

Just changed the name and adjusted the text.

@vitorpamplona vitorpamplona changed the title Relationship status Contact Cards Aug 18, 2025
"kind": 30382,
"tags": [
["d", "e88a691e98d9987c964521dff60025f60700378a4879180dcbbb4a5027850411"],
["n", "follow"],
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

We should probably standardize a "follow" type

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.