# NIP-17 Messaging Implementation Plan

## Background

Didactyl currently uses NIP-04 (kind 4) for all DM communication. NIP-17 send support exists via `nostr_handler_send_dm_nip17()` and the `nostr_dm_send_nip17` tool, but there is **no ability to receive NIP-17 messages** and **all agent responses always use NIP-04**.

The `nostr_core_lib` already has full NIP-17/NIP-59 support including:
- `nostr_nip17_create_chat_event()` — create kind 14 rumor
- `nostr_nip17_send_dm()` — seal + gift wrap (creates wraps for both recipient AND sender)
- `nostr_nip17_receive_dm()` — unwrap gift wrap, unseal rumor, return kind 14
- `nostr_nip17_extract_dm_relays()` — parse kind 10050 relay lists

## Config-Driven Protocol Selection

### New Config Field

Add a `dm_protocol` field to the top-level config:

```json
{
  "dm_protocol": "nip04",
  ...
}
```

Valid values:
- `"nip04"` — NIP-04 only (current behavior, default for backward compatibility)
- `"nip17"` — NIP-17 only (subscribe to kind 1059, send via gift wrap)
- `"both"` — Subscribe to both kind 4 and kind 1059; reply using whichever protocol the message arrived on

### Config Struct Change

In `config.h`, add to `didactyl_config_t`:

```c
typedef enum {
    DM_PROTOCOL_NIP04 = 0,
    DM_PROTOCOL_NIP17 = 1,
    DM_PROTOCOL_BOTH  = 2
} dm_protocol_t;
```

Add `dm_protocol_t dm_protocol;` to the config struct.

## Implementation Steps

### 1. Add `dm_protocol` config parsing

**Files**: `config.h`, `config.c`

- Add `dm_protocol_t` enum and field to `didactyl_config_t`
- Parse `"dm_protocol"` string from JSON in `config_load()`
- Default to `DM_PROTOCOL_NIP04` if not specified

### 2. Add kind 1059 subscription

**File**: `nostr_handler.c` — `nostr_handler_subscribe_dms()`

- When `dm_protocol` is `NIP17` or `BOTH`, add `kind: 1059` to the subscription filter
- When `dm_protocol` is `NIP04` or `BOTH`, keep `kind: 4` in the filter
- The subscription filter becomes `kinds: [4, 1059]` for `BOTH` mode

### 3. Add NIP-17 receive handling in `on_event()`

**File**: `nostr_handler.c` — `on_event()`

Currently `on_event()` rejects anything that is not kind 4. Update to:

- If kind == 1059: unwrap gift wrap via `nostr_nip17_receive_dm()`, extract sender pubkey from the kind 14 rumor, extract message content, determine sender tier, fire `g_dm_callback`
- If kind == 4: existing NIP-04 decrypt path (unchanged)
- Track which protocol was used per sender pubkey for reply routing

### 4. Track protocol per sender for reply routing

**File**: `nostr_handler.c`

Add a small cache that maps `sender_pubkey_hex -> last_protocol_used`:

```c
typedef struct {
    char pubkey_hex[65];
    dm_protocol_t protocol;
} sender_protocol_entry_t;

#define SENDER_PROTOCOL_CACHE_SIZE 64
static sender_protocol_entry_t g_sender_protocol_cache[SENDER_PROTOCOL_CACHE_SIZE];
```

When a DM arrives via kind 4, record `NIP04` for that sender. When via kind 1059, record `NIP17`.

### 5. Add protocol-aware send function

**File**: `nostr_handler.c`

Add a new function that routes based on config + sender history:

```c
int nostr_handler_send_dm_auto(const char* recipient_pubkey_hex, const char* message);
```

Logic:
- If `dm_protocol == NIP04`: always use `nostr_handler_send_dm()`
- If `dm_protocol == NIP17`: always use `nostr_handler_send_dm_nip17()`
- If `dm_protocol == BOTH`: check sender protocol cache; use matching protocol, default to NIP-04

Expose in `nostr_handler.h`.

### 6. Update agent.c to use auto-routing

**File**: `agent.c`

Replace all `nostr_handler_send_dm()` calls with `nostr_handler_send_dm_auto()`. This is a mechanical find-and-replace across ~15 call sites.

### 7. Add kind 10050 startup event support

**File**: `config.json.example`

Add a kind 10050 startup event example so NIP-17 clients can discover the agent's DM relay preferences:

```json
{
  "kind": 10050,
  "content": "",
  "tags": [
    ["relay", "wss://relay.damus.io"],
    ["relay", "wss://nos.lol"]
  ]
}
```

**File**: `config.c` — startup event parsing already handles arbitrary kinds, so this should work with no code changes.

### 8. Update startup DM to use auto-routing

**File**: `main.c`

The startup status DM at line 255 currently calls `nostr_handler_send_dm()`. Update to `nostr_handler_send_dm_auto()`.

## Architecture Diagram

```mermaid
flowchart TD
    subgraph Config
        CFG[dm_protocol setting]
        CFG -->|nip04| NIP04_MODE[NIP-04 only]
        CFG -->|nip17| NIP17_MODE[NIP-17 only]
        CFG -->|both| BOTH_MODE[Both protocols]
    end

    subgraph Subscription
        SUB[nostr_handler_subscribe_dms]
        NIP04_MODE --> SUB_K4[Subscribe kind 4]
        NIP17_MODE --> SUB_K1059[Subscribe kind 1059]
        BOTH_MODE --> SUB_BOTH[Subscribe kind 4 + 1059]
    end

    subgraph Receive Path
        EVT[on_event callback]
        EVT -->|kind 4| DEC4[NIP-04 decrypt]
        EVT -->|kind 1059| DEC17[NIP-17 unwrap + unseal]
        DEC4 --> CACHE[Record sender protocol]
        DEC17 --> CACHE
        CACHE --> CB[Fire dm_callback]
    end

    subgraph Send Path
        SEND[nostr_handler_send_dm_auto]
        SEND -->|config=nip04| S4[nostr_handler_send_dm - kind 4]
        SEND -->|config=nip17| S17[nostr_handler_send_dm_nip17 - kind 1059]
        SEND -->|config=both| LOOKUP[Check sender protocol cache]
        LOOKUP -->|sender used nip04| S4
        LOOKUP -->|sender used nip17| S17
        LOOKUP -->|unknown| S4
    end
```

## File Change Summary

| File | Changes |
|------|---------|
| `config.h` | Add `dm_protocol_t` enum and field |
| `config.c` | Parse `dm_protocol` from JSON |
| `nostr_handler.h` | Add `nostr_handler_send_dm_auto()` declaration |
| `nostr_handler.c` | Add kind 1059 subscription, NIP-17 receive in `on_event()`, sender protocol cache, `nostr_handler_send_dm_auto()` |
| `agent.c` | Replace `nostr_handler_send_dm()` with `nostr_handler_send_dm_auto()` |
| `main.c` | Replace startup DM send with `nostr_handler_send_dm_auto()` |
| `config.json.example` | Add `dm_protocol` field and kind 10050 startup event example |

## Testing Strategy

1. **NIP-04 mode** (default): Verify existing behavior is unchanged
2. **NIP-17 mode**: Send a NIP-17 DM from a client like Amethyst/0xchat, verify Didactyl receives and replies via NIP-17
3. **Both mode**: Send NIP-04 DM, verify NIP-04 reply; send NIP-17 DM, verify NIP-17 reply
4. **Kind 10050**: Verify the relay list event is published on startup and discoverable by NIP-17 clients
