Skip to content

NIP58 - Badge Event "Request" and "Denial"#2204

Open
DoktorShift wants to merge 3 commits intonostr-protocol:masterfrom
DoktorShift:master
Open

NIP58 - Badge Event "Request" and "Denial"#2204
DoktorShift wants to merge 3 commits intonostr-protocol:masterfrom
DoktorShift:master

Conversation

@DoktorShift
Copy link

NIP-58 Extended - Badge Requests

draft optional

This aim for discussion to extend NIP-58 with badge requests and denials.

Motivation

NIP-58 defines badge creation and awarding but provides no mechanism for users to request badges. This extending would add:

  • Users can request badges from issuers
  • Issuers can formally deny requests
  • Users can withdraw requests
  • Issuers can revoke denials

Event Kinds

Kind Name Publisher Type
30009 Badge Definition Issuer Addressable
8 Badge Award Issuer Regular
30008 Profile Badges Recipient Addressable
30058 Badge Request Requester Addressable
30059 Badge Denial Issuer Addressable

Badge Request (kind 30058)

An addressable event where a user requests a badge from an issuer.

{
  "kind": 30058,
  "content": "<message to issuer>",
  "tags": [
    ["d", "30009:<issuer-pubkey>:<badge-d-tag>"],
    ["a", "30009:<issuer-pubkey>:<badge-d-tag>", "<relay-url>"],
    ["p", "<issuer-pubkey>"]
  ]
}
  • The d tag MUST be the badge's a tag identifier
  • This ensures one active request per badge per user
  • Publishing a new request for the same badge updates/replaces the previous one

Proof Tag

Requesters MAY include evidence to support their request:

["proof", "<url, text, or nostr event id>"]

Multiple proof tags are allowed. Verification method is left to the issuer.

Withdrawal

To withdraw a request, publish with status tag:

{
  "kind": 30058,
  "content": "",
  "tags": [
    ["d", "30009:<issuer-pubkey>:<badge-d-tag>"],
    ["a", "30009:<issuer-pubkey>:<badge-d-tag>"],
    ["p", "<issuer-pubkey>"],
    ["status", "withdrawn"]
  ]
}

Clients MUST ignore or CAN honor requests with ["status", "withdrawn"].

Badge Denial (kind 30059)

An addressable event where an issuer formally denies a badge request.

{
  "kind": 30059,
  "content": "<reason for denial>",
  "tags": [
    ["d", "<request-event-id>"],
    ["a", "30009:<issuer-pubkey>:<badge-d-tag>", "<relay-url>"],
    ["e", "<request-event-id>", "<relay-url>"],
    ["p", "<requester-pubkey>"]
  ]
}
  • The d tag MUST be the request event id
  • This ensures one denial per request event

Soft Denial

Denials are soft by design. A denied user CAN submit a new request for the same badge (e.g., with better proof). The new request generates a new event id, making the old denial obsolete.

Revocation

To revoke a denial, publish with status tag:

{
  "kind": 30059,
  "content": "",
  "tags": [
    ["d", "<request-event-id>"],
    ["a", "30009:<issuer-pubkey>:<badge-d-tag>"],
    ["e", "<request-event-id>"],
    ["p", "<requester-pubkey>"],
    ["status", "revoked"]
  ]
}

Clients MUST ignore denials with ["status", "revoked"].

Request States

A request can be in one of four states:

State Priority Condition
Fulfilled 1 (highest) Badge Award (kind 8) exists for this badge+user
Denied 2 Badge Denial (kind 30059) exists for this request
Withdrawn 3 Request has ["status", "withdrawn"]
Pending 4 (lowest) Request exists, none of the above

State Resolution

1. Check for Badge Award (kind 8) from issuer to requester for this badge
   → If exists: FULFILLED

2. Check request for ["status", "withdrawn"]
   → If exists: WITHDRAWN

3. Check for Badge Denial (kind 30059) referencing this request
   → If exists and not ["status", "revoked"]: DENIED

4. Otherwise: PENDING

Edge Cases

Scenario Result
Award exists + Denial exists Fulfilled (award takes precedence)
Request withdrawn + Denial exists Withdrawn (denial invalid)
User re-requests after denial New pending request (old denial obsolete)
Denial without valid request Invalid (clients ignore)

Querying

Issuer finds incoming requests:

{"kinds": [30058], "#p": ["<issuer-pubkey>"]}

User finds own outgoing requests:

{"kinds": [30058], "authors": ["<user-pubkey>"]}

User finds denials:

{"kinds": [30059], "#p": ["<user-pubkey>"]}

Protocol Flow

USER                                              ISSUER
  │                                                  │
  │         ┌─────────────────────────┐              │
  │         │ Badge Definition        │              │
  │         │ (kind 30009)            │◀─────────────┤
  │         └───────────┬─────────────┘              │
  │                     │                            │
  │              discover                            │
  │                     ▼                            │
  │         ┌─────────────────────────┐              │
  ├────────▶│ Badge Request           │              │
  │         │ (kind 30058)            │─────────────▶│
  │         │ + optional proof        │              │
  │         └───────────┬─────────────┘              │
  │                     │                            │
  │        ┌────────────┤                   ┌────────┴────────┐
  │        │            │                   │     REVIEW      │
  │        ▼            │                   └────────┬────────┘
  │  ┌───────────┐      │              ┌─────────────┼─────────────┐
  │  │ WITHDRAW  │      │              │             │             │
  │  │ status:   │      │              ▼             ▼             ▼
  │  │ withdrawn │      │          APPROVE         DENY         IGNORE
  │  └───────────┘      │              │             │
  │                     │              ▼             ▼
  │                     │         ┌────────┐   ┌──────────┐
  │                     │         │ Award  │   │ Denial   │
  │                     │         │ kind 8 │   │kind 30059│
  │                     │         └───┬────┘   └────┬─────┘
  │                     │             │             │
  │◀──────────────────────────────────┘             │
  │  FULFILLED                                      │
  │                                                 │
  │◀────────────────────────────────────────────────┘
  │  DENIED (can re-request)
  │
  │         ┌─────────────────────────┐
  ├────────▶│ Accept to Profile       │
  │         │ (kind 30008)            │
  │         └─────────────────────────┘

Examples

Request with Proof

{
  "kind": 30058,
  "pubkey": "a1b2c3...",
  "created_at": 1704067200,
  "content": "I contributed the auth module!",
  "tags": [
    ["d", "30009:x1y2z3:contributor"],
    ["a", "30009:x1y2z3:contributor", "wss://relay.damus.io"],
    ["p", "x1y2z3..."],
    ["proof", "https://github.com/project/repo/pull/42"]
  ]
}

Denial with Reason

{
  "kind": 30059,
  "pubkey": "x1y2z3...",
  "created_at": 1704153600,
  "content": "Please provide ticket confirmation.",
  "tags": [
    ["d", "req123..."],
    ["a", "30009:x1y2z3:attendee"],
    ["e", "req123...", "wss://relay.damus.io"],
    ["p", "a1b2c3..."]
  ]
}

Backward Compatibility

Fully backward compatible with NIP-58:

  • Existing kind 30009, 8, 30008 events unchanged
  • Badge award without request still works
  • Clients unaware of 30058/30059 simply ignore them

Security Considerations

  • Proof is self-reported; issuers must verify
  • Requests are public; users should be aware
  • Denial reasons are public; issuers may leave empty
  • Clients should rate-limit requests to prevent spam

@DoktorShift DoktorShift changed the title Badge Event "Request" and "Denial" NIP58 - Badge Event "Request" and "Denial" Jan 28, 2026
@vitorpamplona
Copy link
Collaborator

Why don't we use non-replaceable for the new kinds? You wouldn't need to publish withdrawals, just delete the event with a kind 5

@DoktorShift
Copy link
Author

Good point, thanks for the feedback! @vitorpamplona
i'll update the PR. Withdrawal and revocation patterns can then simply use kind 5 deletion (NIP-09).

Feedback updates.

Removed new introduced status tag. 
Use NIP-9 kind 5 instead
@DoktorShift
Copy link
Author

Hey @vitorpamplona , any concerns or open requests pending?

I already implemented the approach iinto an Webapplication called BadgeBox. Its working great :)

@vitorpamplona
Copy link
Collaborator

Looks good to me. We just need more implementers before merging

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.

2 participants