> ## Documentation Index
> Fetch the complete documentation index at: https://mintlify.com/nullclaw/nullclaw/llms.txt
> Use this file to discover all available pages before exploring further.

# Nostr Channel Setup

> Configure NullClaw to communicate via Nostr relays using NIP-17 gift-wrapped DMs and NIP-04 encrypted messages

NullClaw speaks Nostr natively via NIP-17 (gift-wrapped private DMs) and NIP-04 (legacy encrypted DMs), enabling decentralized, censorship-resistant AI assistant communication.

## Prerequisites

* [`nak`](https://github.com/fiatjaf/nak) CLI tool installed and in `$PATH`
* Your Nostr public key (npub or hex format)
* Access to Nostr relays

## Installation

### Install nak

<Tabs>
  <Tab title="Go Install">
    ```bash theme={null}
    go install github.com/fiatjaf/nak@latest
    ```
  </Tab>

  <Tab title="Homebrew">
    ```bash theme={null}
    brew install nak
    ```
  </Tab>

  <Tab title="Binary Download">
    Download from [GitHub releases](https://github.com/fiatjaf/nak/releases)
  </Tab>
</Tabs>

Verify installation:

```bash theme={null}
nak --version
```

## Quick Setup

<Steps>
  <Step title="Run Onboarding Wizard">
    ```bash theme={null}
    nullclaw onboard --interactive
    ```

    Select Nostr when prompted for channels.
  </Step>

  <Step title="Generate or Import Key">
    Wizard will offer to generate a new keypair or import existing private key.
  </Step>

  <Step title="Provide Owner Pubkey">
    Enter your personal Nostr pubkey (npub or hex) - this is always allowed through DM policy.
  </Step>

  <Step title="Configure Relays">
    Add relays for listening and publishing events.
  </Step>
</Steps>

The wizard encrypts your private key with ChaCha20-Poly1305 and stores it as `enc2:...` in config.

## Configuration

### Basic Setup

Edit `~/.nullclaw/config.json`:

```json config.json theme={null}
{
  "channels": {
    "nostr": {
      "private_key": "enc2:ChaCha20Poly1305-encrypted-hex-key",
      "owner_pubkey": "your-hex-pubkey-or-npub",
      "relays": [
        "wss://relay.damus.io",
        "wss://nos.lol",
        "wss://relay.nostr.band"
      ],
      "dm_allowed_pubkeys": ["*"]
    }
  }
}
```

### Complete Configuration

```json config.json theme={null}
{
  "channels": {
    "nostr": {
      "private_key": "enc2:ChaCha20Poly1305-encrypted-hex-key",
      "owner_pubkey": "npub1...",
      "relays": [
        "wss://relay.damus.io",
        "wss://nos.lol",
        "wss://relay.nostr.band"
      ],
      "dm_allowed_pubkeys": ["*"],
      "display_name": "NullClaw AI",
      "about": "AI assistant running on Nostr via NullClaw",
      "nip05": "nullclaw@yourdomain.com",
      "lnurl": "lnurl1..."
    }
  }
}
```

## Configuration Options

### private\_key (required)

Your bot's Nostr private key (nsec or hex), encrypted at rest:

```json theme={null}
"private_key": "enc2:ChaCha20Poly1305-encrypted-hex-key"
```

<Warning>
  Private keys are encrypted with ChaCha20-Poly1305 and only decrypted into memory while the channel is running. Keys are zeroed on channel stop.
</Warning>

<Accordion title="Manual key generation">
  ```bash theme={null}
  # Generate new key with nak
  nak key generate

  # Output format:
  # nsec1... (private key)
  # npub1... (public key)
  ```

  Store the `nsec1...` value in config. NullClaw will encrypt it on first run if `secrets.encrypt` is enabled.
</Accordion>

### owner\_pubkey (required)

Your personal Nostr public key (always allowed regardless of `dm_allowed_pubkeys`):

```json theme={null}
"owner_pubkey": "npub1qpq..." // or hex format
```

This is YOUR pubkey, not the bot's. The bot will always accept DMs from this pubkey.

### relays (required)

Nostr relays for publishing and listening:

```json theme={null}
"relays": [
  "wss://relay.damus.io",
  "wss://nos.lol",
  "wss://relay.nostr.band",
  "wss://nostr.wine",
  "wss://relay.snort.social"
]
```

<Accordion title="Recommended relay sets">
  **General purpose:**

  ```json theme={null}
  [
    "wss://relay.damus.io",
    "wss://nos.lol",
    "wss://relay.nostr.band"
  ]
  ```

  **Paid relays (better spam filtering):**

  ```json theme={null}
  [
    "wss://nostr.wine",
    "wss://relay.nostr.band",
    "wss://relay.nos.social"
  ]
  ```

  **Privacy-focused:**

  ```json theme={null}
  [
    "wss://nostr.fmt.wiz.biz",
    "wss://relay.nostr.bg"
  ]
  ```
</Accordion>

### dm\_allowed\_pubkeys

Pubkeys allowed to DM the bot:

<Tabs>
  <Tab title="Allow All">
    ```json theme={null}
    "dm_allowed_pubkeys": ["*"]
    ```

    Anyone can message the bot.
  </Tab>

  <Tab title="Specific Users">
    ```json theme={null}
    "dm_allowed_pubkeys": [
      "npub1abc...",
      "hex-pubkey-2"
    ]
    ```

    Only listed pubkeys can message.
  </Tab>

  <Tab title="Owner Only">
    ```json theme={null}
    "dm_allowed_pubkeys": []
    ```

    Only `owner_pubkey` can message (most restrictive).
  </Tab>
</Tabs>

<Note>
  `owner_pubkey` is ALWAYS allowed, regardless of this setting.
</Note>

### Profile Fields (Optional)

Configure bot's Nostr profile metadata:

```json theme={null}
"display_name": "NullClaw AI",
"about": "AI assistant powered by NullClaw - send me a DM!",
"nip05": "bot@yourdomain.com",
"lnurl": "lnurl1...",
"picture": "https://yourdomain.com/avatar.png"
```

## How It Works

### NIP-17 Gift Wrapped DMs

NullClaw's primary DM protocol:

1. **Listening**: Bot subscribes to kind:10050 inbox relays
2. **Receiving**: Unwraps gift-wrapped DMs using `nak gift unwrap`
3. **Deduplication**: Tracks rumor IDs to prevent duplicate deliveries across relays
4. **Sending**: Wraps replies with `nak gift wrap` and publishes to sender's relays

### NIP-04 Legacy DMs

Backward compatibility for older clients:

1. **Receiving**: Listens for kind:4 events
2. **Decrypting**: Uses `nak decrypt`
3. **Sending**: Mirrors sender's protocol (NIP-04 reply to NIP-04 sender)

### Protocol Mirroring

Bot automatically remembers which protocol each sender used and replies using the same protocol.

## Startup Flow

<Steps>
  <Step title="Decrypt Private Key">
    Bot decrypts `enc2:...` private key into memory.
  </Step>

  <Step title="Announce Inbox">
    Publishes kind:10050 event announcing DM inbox relays.
  </Step>

  <Step title="Start Listener">
    Spawns `nak req --stream` subprocess for event listening.
  </Step>

  <Step title="Process Events">
    Reader thread processes incoming NIP-17 and NIP-04 events.
  </Step>
</Steps>

## Running the Channel

### Gateway Mode

```bash theme={null}
nullclaw gateway
```

Starts all configured channels including Nostr.

### Standalone Channel

```bash theme={null}
nullclaw channel start nostr
```

### Check Status

```bash theme={null}
nullclaw channel status
```

Shows:

* Connection status
* Active relays
* Message count
* Last activity

## Security

<AccordionGroup>
  <Accordion title="Encrypted Private Keys">
    Private keys are encrypted with ChaCha20-Poly1305:

    ```json theme={null}
    {
      "secrets": {
        "encrypt": true
      }
    }
    ```

    Keys are only decrypted into memory while channel is running.
  </Accordion>

  <Accordion title="Pubkey Allowlists">
    Restrict who can message your bot:

    ```json theme={null}
    "dm_allowed_pubkeys": ["npub1...", "npub2..."]
    ```

    Empty array = owner only.
  </Accordion>

  <Accordion title="Key Zeroing">
    On channel stop, private keys are zeroed in memory before free:

    ```zig theme={null}
    @memset(sec, 0);
    allocator.free(sec);
    ```
  </Accordion>

  <Accordion title="Rumor Deduplication">
    Prevents replay attacks by tracking seen rumor IDs with timestamps.
  </Accordion>
</AccordionGroup>

## Troubleshooting

### nak command not found

```bash theme={null}
# Check if nak is installed
which nak

# Install with Go
go install github.com/fiatjaf/nak@latest

# Add Go bin to PATH
export PATH="$PATH:$(go env GOPATH)/bin"
```

### Bot Not Receiving Messages

<Steps>
  <Step title="Check Relays">
    Verify sender and bot share at least one relay.
  </Step>

  <Step title="Check Allowlist">
    Ensure sender's pubkey is in `dm_allowed_pubkeys` or use `["*"]`.
  </Step>

  <Step title="Check Logs">
    Run `nullclaw gateway` in foreground to see event processing.
  </Step>

  <Step title="Test with Owner">
    Send DM from `owner_pubkey` (always allowed).
  </Step>
</Steps>

### Duplicate Messages

If you receive duplicate responses:

* Check if same rumor is delivered via multiple relays
* NullClaw should deduplicate automatically
* Check logs for "duplicate rumor" messages

### Private Key Decryption Failed

```bash theme={null}
# Re-encrypt secrets
nullclaw onboard --channels-only

# Or manually edit config with plaintext nsec, NullClaw will encrypt on start
```

### Relay Connection Issues

```bash theme={null}
# Test relay connectivity with nak
nak req -k 1 --limit 1 wss://relay.damus.io

# Try different relays
# Remove slow/unreliable relays from config
```

## Advanced Usage

### External Signing (bunker://)

Use external signer instead of embedded private key:

```json config.json theme={null}
{
  "channels": {
    "nostr": {
      "private_key": "bunker://pubkey?relay=wss://relay.example.com",
      "owner_pubkey": "npub1...",
      "relays": ["wss://relay.damus.io"]
    }
  }
}
```

NullClaw passes `bunker://` URIs directly to `nak --sec`.

### Multiple Nostr Accounts

Currently, Nostr channel doesn't support multiple accounts (unlike Telegram/Discord). To run multiple bots:

1. Run separate NullClaw instances
2. Use different config directories
3. Different ports for each instance

### Kind:10050 Inbox Relays

Bot automatically publishes kind:10050 (inbox relays) on startup, announcing where it listens for gift-wrapped DMs.

Clients following NIP-17 will look up your inbox relays and publish there.

## Protocol Details

### Event Kinds

| Kind  | Protocol | Description             |
| ----- | -------- | ----------------------- |
| 4     | NIP-04   | Legacy encrypted DMs    |
| 14    | NIP-17   | Rumor (inner event)     |
| 1059  | NIP-17   | Gift wrap (outer event) |
| 10050 | NIP-17   | Inbox relay list        |

### Timestamps

Bot discards events with `created_at` before channel start time, preventing old messages from being processed on restart.

### Session Keys

Session keys are derived from:

* Sender pubkey
* Channel account ID
* Protocol type (nostr)

Format: `nostr:main:<sender_pubkey>`

## Performance

* **Startup**: \<2ms (excluding nak subprocess spawn)
* **Message latency**: Depends on relay response time (typically 100-500ms)
* **Memory**: \~500KB per active session
* **Relay connections**: Persistent WebSocket to each relay

## Next Steps

<CardGroup cols={2}>
  <Card title="Sandboxing" href="/guides/sandboxing" icon="shield">
    Configure security sandboxing
  </Card>

  <Card title="Memory Backends" href="/guides/memory-backends" icon="database">
    Set up vector memory
  </Card>
</CardGroup>
