Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion .github/workflows/ci.yml
Original file line number Diff line number Diff line change
Expand Up @@ -108,7 +108,7 @@ jobs:
- name: Setup dotnet
uses: actions/setup-dotnet@v4
with:
dotnet-version: '9.0.x'
dotnet-version: '10.0.x'

- name: Check
working-directory: snippets/csharp
Expand Down
22 changes: 22 additions & 0 deletions snippets/csharp/Nip09.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,22 @@
namespace Snippets;

using Nostr.Sdk;

public class Nip09
{
public static void Run()
{
var keys = Keys.Parse("3501454135014541350145413501453fefb02227e449e57cf4d3a3ce05378683");

// ANCHOR: build
// Construct the request
var id = EventId.Parse("7469af3be8c8e06e1b50ef1caceba30392ddc0b6614507398b7d7daa4c218e96");
var request = new EventDeletionRequest(ids: [id], coordinates: [], reason: "these posts were published by accident");

// Build the event
Event e = EventBuilder.Delete(request).SignWithKeys(keys);
// ANCHOR_END: build

Console.WriteLine($"Event deletion request: {e.AsPrettyJson()}");
}
}
3 changes: 3 additions & 0 deletions snippets/csharp/Program.cs
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,9 @@ public static async Task Main()
// Event Building
await EventBuilding.Build();

// NIP09
Nip09.Run();

// NIP44
Nip44.Run();

Expand Down
2 changes: 1 addition & 1 deletion snippets/csharp/Snippets.csproj
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@

<PropertyGroup>
<OutputType>Exe</OutputType>
<TargetFramework>net9.0</TargetFramework>
<TargetFramework>net10.0</TargetFramework>
<ImplicitUsings>enable</ImplicitUsings>
<Nullable>enable</Nullable>
</PropertyGroup>
Expand Down
24 changes: 24 additions & 0 deletions snippets/js/src/nip09.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,24 @@
import {EventBuilder, EventDeletionRequest, EventId, Keys, loadWasmSync} from "@rust-nostr/nostr-sdk";

function run() {
// Load WASM
loadWasmSync();

let keys = Keys.parse("3501454135014541350145413501453fefb02227e449e57cf4d3a3ce05378683");

// ANCHOR: build
// Construct the request
let id = EventId.parse("7469af3be8c8e06e1b50ef1caceba30392ddc0b6614507398b7d7daa4c218e96");
let request = new EventDeletionRequest();
request.ids = [id];
request.reason = "these posts were published by accident";


// Build the event
let event = EventBuilder.delete(request).signWithKeys(keys);
// ANCHOR_END: build

console.log("Event deletion request: " + event.asPrettyJson())
}

run();
24 changes: 24 additions & 0 deletions snippets/kotlin/src/main/kotlin/nip09.kt
Original file line number Diff line number Diff line change
@@ -0,0 +1,24 @@
package rust.nostr.snippets

// ANCHOR: full
import rust.nostr.sdk.*

fun nip09() {
val keys: Keys = Keys.parse("3501454135014541350145413501453fefb02227e449e57cf4d3a3ce05378683")

// ANCHOR: build
// Construct the request
val id = EventId.parse("7469af3be8c8e06e1b50ef1caceba30392ddc0b6614507398b7d7daa4c218e96")
val request = EventDeletionRequest(ids = listOf(id), coordinates = listOf(), reason = "these posts were published by accident")

// Build the event
val event: Event = EventBuilder.delete(request).signWithKeys(keys)
// ANCHOR_END: build

println("Event deletion request: ${event.asPrettyJson()}")
}

fun main() {
nip09()
}
// ANCHOR_END: full
21 changes: 21 additions & 0 deletions snippets/python/src/nip09.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,21 @@
# ANCHOR: full
from nostr_sdk import Keys, EventDeletionRequest, EventId, Event, EventBuilder


def nip09():
keys: Keys = Keys.parse("3501454135014541350145413501453fefb02227e449e57cf4d3a3ce05378683")

# ANCHOR: build
# Construct the request
id = EventId.parse("7469af3be8c8e06e1b50ef1caceba30392ddc0b6614507398b7d7daa4c218e96")
request = EventDeletionRequest(ids=[id], coordinates=[], reason="these posts were published by accident")

# Build the event
event: Event = EventBuilder.delete(request).sign_with_keys(keys)
# ANCHOR_END: build

print(f"Event deletion request: {event.as_pretty_json()}")

if __name__ == '__main__':
nip09()
# ANCHOR_END: full
1 change: 1 addition & 0 deletions snippets/rust/src/main.rs
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,7 @@ mod nip47;
mod nip49;
mod nip59;
mod req;
mod nip09;

#[tokio::main]
async fn main() {}
23 changes: 23 additions & 0 deletions snippets/rust/src/nip09.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,23 @@
// ANCHOR: full
use nostr_sdk::prelude::*;

pub fn nip09() -> Result<()> {
let keys: Keys = Keys::parse("3501454135014541350145413501453fefb02227e449e57cf4d3a3ce05378683")?;

// ANCHOR: build
// Construct the request
let request = EventDeletionRequest::new()
.id(EventId::from_hex("7469af3be8c8e06e1b50ef1caceba30392ddc0b6614507398b7d7daa4c218e96")?)
// optionally add coordinates for replaceable events
// .coordinate(...)
.reason("these posts were published by accident");

// Build the event
let event: Event = EventBuilder::delete(request).sign_with_keys(&keys)?;
// ANCHOR_END: build

println!("Event deletion request: {}", event.as_pretty_json());

Ok(())
}
// ANCHOR_END: full
20 changes: 20 additions & 0 deletions snippets/swift/Sources/Nip09.swift
Original file line number Diff line number Diff line change
@@ -0,0 +1,20 @@
// ANCHOR: full
import Foundation
import NostrSDK

func nip09() throws {
let keys = try Keys.parse(secretKey: "3501454135014541350145413501453fefb02227e449e57cf4d3a3ce05378683")

// ANCHOR: build
// Construct the request
let id = try EventId.parse(id: "7469af3be8c8e06e1b50ef1caceba30392ddc0b6614507398b7d7daa4c218e96")
let request = EventDeletionRequest(ids: [id], coordinates: [], reason: "these posts were published by accident")

// Build the event
let builder = EventBuilder.delete(request: request)
let event = try builder.signWithKeys(keys: keys)
// ANCHOR_END: build

print("Event deletion request: \(try event.asPrettyJson())");
}
// ANCHOR_END: full
1 change: 1 addition & 0 deletions src/SUMMARY.md
Original file line number Diff line number Diff line change
Expand Up @@ -37,6 +37,7 @@
- [NIP-05: Mapping nostr keys to DNS-based internet identifiers](sdk/nips/05.md)
- [NIP-06: Key derivation from seed phrase](sdk/nips/06.md)
- [NIP-07: Browser Extension Signer](sdk/nips/07.md)
- [NIP-09: Event Deletion Request](sdk/nips/09.md)
- [NIP-17: Private Direct Messages](sdk/nips/17.md)
- [NIP-19: bech32-encoded entities](sdk/nips/19.md)
- [NIP-21: nostr URI scheme](sdk/nips/21.md)
Expand Down
88 changes: 88 additions & 0 deletions src/sdk/nips/09.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,88 @@
# NIP-09: Event Deletion Request

[NIP-09] defines a best-effort mechanism for retracting events you previously published.
It does **not** guarantee erasure—relays and downstream clients are allowed to cache or ignore deletion requests—so treat it as a courtesy protocol for well-behaved peers.

## When to send a deletion event

1. You control the secret key that signed the original event.
2. You know the event IDs (or coordinates for replaceable/parameterized events) you want to retract.
3. You accept that the payload remains public even after the deletion request propagates.

## Building the request

<custom-tabs category="lang">

<div slot="title">Rust</div>
<section>

```rust,ignore
{{#include ../../../snippets/rust/src/nip09.rs:build}}
```

</section>

<div slot="title">Python</div>
<section>

```python,ignore
{{#include ../../../snippets/python/src/nip09.py:build}}
```

</section>

<div slot="title">JavaScript</div>
<section>

```typescript,ignore
{{#include ../../../snippets/js/src/nip09.ts:build}}
```

</section>

<div slot="title">Kotlin</div>
<section>

```kotlin,ignore
{{#include ../../../snippets/kotlin/src/main/kotlin/nip09.kt:build}}
```

</section>

<div slot="title">Swift</div>
<section>

```swift,ignore
{{#include ../../../snippets/swift/Sources/Nip09.swift:build}}
```

</section>

<div slot="title">C#</div>
<section>

```cs,ignore
{{#include ../../../snippets/csharp/Nip09.cs:build}}
```

</section>

<div slot="title">Flutter</div>
<section>

TODO

</section>
</custom-tabs>

Then, the deletion event can be sent to every relay that received the original event.

## Caveats

- Relays are free to ignore deletion events entirely or only apply them to new subscribers.
- Archive relays and scrapers can continue serving the old content indefinitely.
- Deleting a parameterized replaceable event without its coordinate will have no effect.

In short: use [NIP-09] as part of your UX, but do not treat it as a hard delete.

[NIP-09]: https://github.com/nostr-protocol/nips/blob/master/09.md
Loading