Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
71 commits
Select commit Hold shift + click to select a range
f3085e7
local first
frnandu Jun 22, 2026
3710df6
feat: local first
frnandu Jun 23, 2026
d9ee947
feat: local first
frnandu Jun 23, 2026
4165f12
feat: local first
frnandu Jun 23, 2026
b3a85a1
resolve circular betwee broadcast & pending_broadcast_delivery by int…
frnandu Jun 23, 2026
afd43ce
don't use outbox naming here
frnandu Jun 24, 2026
925c4ea
naming
frnandu Jun 24, 2026
cab1046
no need to sign, broadcast signs it from accounts
frnandu Jun 24, 2026
1aa5b81
add missing accounts on new ndk instance
frnandu Jun 24, 2026
5bdf778
remove uneeded _reservePort
frnandu Jun 24, 2026
783184c
remove uneeded _reservePort
frnandu Jun 24, 2026
fb5a107
remove uneeded _reservePort
frnandu Jun 24, 2026
df78c54
remove separate storage of metadatas & contactLists
frnandu Jun 24, 2026
5b8202c
remove RelayDeliveryTargetRecord, only RelayDeliveryTarget needed
frnandu Jun 24, 2026
e86cbbe
fix missing drift methods
frnandu Jun 24, 2026
11b487b
fix missing objectbox methods
frnandu Jun 24, 2026
7782705
add resetTransport method
frnandu Jun 25, 2026
c3ed7fe
fix tests after addind resetTransport method
frnandu Jun 25, 2026
3c11729
fix tests after addind resetTransport method
frnandu Jun 25, 2026
997c192
fix tests after addind resetTransport method
frnandu Jun 25, 2026
f6180a2
remove cashu seed phrase warning from constructor
frnandu Jun 25, 2026
a73b22f
Merge branch 'master' into feat/local-first
frnandu Jun 25, 2026
ecdcf66
DecryptedEventPayload
frnandu Jun 25, 2026
17c2def
drift_flutter: ^0.3.0
frnandu Jun 26, 2026
77e20f0
cache eviction
frnandu Jun 26, 2026
974067e
cache eviction
frnandu Jun 26, 2026
f814021
fix docs about new packages
frnandu Jun 27, 2026
c6f6817
amber -> nip55 naming
frnandu Jun 27, 2026
2d4802f
doc delivery policy
frnandu Jun 27, 2026
2e1b3c8
feat: add local-first interactive signer retry flow
frnandu Jun 27, 2026
67c07e6
fix naming in drift
frnandu Jun 27, 2026
b4b54ac
cleanup
frnandu Jun 27, 2026
cfc8cd9
dart test -j 4
frnandu Jun 27, 2026
3a139d5
dart test -j 2
frnandu Jun 27, 2026
7017e33
dms tests
frnandu Jun 27, 2026
7a3530b
giftwrap cache tests
frnandu Jun 27, 2026
766f443
patch target 80%
frnandu Jun 27, 2026
f1d892d
Merge branch 'master' into feat/local-first
frnandu Jun 27, 2026
8d267bf
add ndk_flutter & cli documentation
frnandu Jun 27, 2026
0cf1205
improve docs
frnandu Jun 27, 2026
a6ce4ee
improve docs
frnandu Jun 29, 2026
5aa4e5e
fixes
frnandu Jun 29, 2026
5886323
fixes
frnandu Jun 29, 2026
d9dacbb
treat duplcate as ack by relay
frnandu Jun 30, 2026
a15e5f4
fix: sweep/hide NIP-09 coordinate (a-tag) deletions across cache back…
frnandu Jun 30, 2026
cb11eef
fix flaky tests
frnandu Jun 30, 2026
bae42d2
drift_flutter: ^0.2.8
frnandu Jul 1, 2026
284d508
Merge branch 'master' into feat/local-first
frnandu Jul 1, 2026
71c575e
EventCacheStateRecord
frnandu Jul 1, 2026
f60f108
fix tests
frnandu Jul 1, 2026
d30e67b
fix tests
frnandu Jul 1, 2026
06e3a42
add more tests
frnandu Jul 1, 2026
43d65bd
remove Kotlin Gradle Plugin
frnandu Jul 3, 2026
01ea7ce
remove Kotlin Gradle Plugin
frnandu Jul 3, 2026
34e8267
ephemeral event better handling in cache
frnandu Jul 6, 2026
c96944f
fix: sweep stale delivery records during eviction
frnandu Jul 7, 2026
7666698
fix: sweep stale delivery records during eviction
frnandu Jul 7, 2026
4aeecc9
don't retry sending/signing expired event
frnandu Jul 8, 2026
6494a87
mobile_scanner: ^7.2.1
frnandu Jul 15, 2026
ef9013b
Merge branch 'master' into feat/local-first
frnandu Jul 15, 2026
6308fd3
fomart
frnandu Jul 15, 2026
675b3a8
Merge remote-tracking branch 'origin/master' into feat/local-first
frnandu Jul 15, 2026
a2007e6
fix: dart format (match CI environment without pub get)
frnandu Jul 15, 2026
d8044c1
fix analyze issues
frnandu Jul 15, 2026
341262a
fix: dart format quantum_secure_page.dart
frnandu Jul 15, 2026
a12cb3e
fix: dart format quantum_secure_page.dart (unresolved-flutter_lints w…
frnandu Jul 15, 2026
3c87573
add SK translations
frnandu Jul 18, 2026
f6cbad9
remove timeout
frnandu Jul 22, 2026
9b99acf
fix: dart format all packages (CI format check)
frnandu Jul 23, 2026
eb4b19a
fix: dart format with standalone SDK 3.12.2 (match CI)
frnandu Jul 24, 2026
7fdbebf
fix: dart format generated sample_app_localizations.dart
frnandu Jul 24, 2026
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
The table of contents is too big for display.
Diff view
Diff view
  •  
  •  
  •  
9 changes: 5 additions & 4 deletions .github/workflows/tests.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -19,16 +19,17 @@ jobs:
- run: dart pub get
- run: dart format --output=none .
- run: dart analyze --no-fatal-warnings
- run: dart test --coverage="coverage"
- run: dart test ./example
- run: dart test --coverage="coverage" -j 2
- run: dart test ./example -j 2
- run: dart pub global activate coverage
- run: $HOME/.pub-cache/bin/format_coverage --ignore-files **/*.g.dart --lcov --check-ignore --in=coverage --out=coverage.lcov --report-on=lib
- name: Upload coverage reports to Codecov
uses: codecov/codecov-action@v3
uses: codecov/codecov-action@v7
env:
CODECOV_TOKEN: ${{ secrets.CODECOV_TOKEN }}
with:
files: coverage.lcov
fail_ci_if_error: true # fails only on upload errors, not coverage drops
verbose: true # optional (default = false)

nip07-tests:
Expand All @@ -48,4 +49,4 @@ jobs:
architecture: x64 # optional, x64 or arm
- run: dart pub get
- run: dart analyze --no-fatal-warnings
- run: flutter test --platform chrome
- run: flutter test --platform chrome --concurrency=2
6 changes: 3 additions & 3 deletions doc/concepts/signers.md
Original file line number Diff line number Diff line change
Expand Up @@ -11,11 +11,11 @@ Signers are responsible for cryptographic operations: signing events, encrypting
| `NdkEventSigner` | Automatic platform selection (web/native) | No (instant) |
| `Nip46EventSigner` | Remote signer via NIP-46 bunker | Yes |
| `Nip07EventSigner` | Browser extension (NIP-07) | Yes |
| `AmberEventSigner` | Android Amber app | Yes |
| `Nip55EventSigner` | Android NIP-55 external signer app | Yes |

## Pending Requests

External signers (NIP-46, NIP-07, Amber) require user approval for operations. The unified pending requests API allows your UI to:
External signers (NIP-46, NIP-07, NIP-55) require user approval for operations. The unified pending requests API allows your UI to:

- Display pending operations to the user
- Cancel pending requests
Expand Down Expand Up @@ -82,7 +82,7 @@ try {

### Handling Remote Rejections

When the user rejects a request on the remote signer (bunker, browser extension, Amber), the caller receives a `SignerRequestRejectedException`:
When the user rejects a request on the remote signer (bunker, browser extension, NIP-55 signer app), the caller receives a `SignerRequestRejectedException`:

```dart
try {
Expand Down
106 changes: 106 additions & 0 deletions doc/guides/cache-behavior.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,106 @@
---
icon: database
order: 97
---

# Cache Behavior

NDK's cache persists event data, delivery state, and decrypted payload sidecars used by app-facing reads and writes.

This guide focuses on the behavior you can rely on when building an app with NDK. It does **not** cover backend implementation details.

## What gets cached

At a high level, NDK may persist:

- Nostr events
- event source relays
- pending delivery state for locally created events
- decrypted plaintext sidecars for encrypted events
- convenience projections like metadata, contact lists, and user relay lists

## What `load`-style reads mean

When NDK reads events from cache, it applies these visibility rules:

- expired events are hidden
- author-deleted events are hidden
- for replaceable and addressable events, only the latest visible winner is returned

App-facing reads return the current logical state, not raw historical storage.

## Event sources vs delivery targets

These are different concepts:

- **Event sources** answer: "where did this event come from?"
- **Delivery targets** answer: "where does this locally created event still need to be sent?"

As an app developer:

- use normal usecases and requests for reading and publishing
- only inspect low-level source relay APIs if your app explicitly needs provenance
- do not assume a source relay is automatically a pending broadcast target

## Local-first publishing

When your app broadcasts an event through normal NDK APIs:

- the event can become visible locally before every relay acknowledges it
- relay delivery progress can survive app restarts when the cache backend is persistent
- retries can continue in the background while the app is running
- replaceable events retry only the newest visible version

This lets the UI update from locally persisted state without waiting for every relay to accept the event.

## Encrypted payload cache

For encrypted content, NDK can cache decrypted plaintext separately from the original event.

This is useful when:

- the app needs to render the same DM or private list repeatedly
- decryption depends on a slow or remote signer

Important behavior:

- the original event stays encrypted in the canonical event store
- plaintext is cached in a separate sidecar keyed by event and viewer
- different viewers may have different cached plaintext results

## Convenience accessors

`metadata`, `follows`, and `userRelayLists` are high-level app APIs backed by the generic event cache.

In practice this means:

- you should keep using the high-level usecases
- you should not maintain a separate storage assumption for metadata/contact lists
- replaceable and deletion semantics are applied consistently across generic event reads and convenience accessors

## Cache eviction

Eviction behavior is:

- removing expired events
- removing author-deleted events
- removing superseded replaceable/addressable events
- optionally applying caps to visible events per kind

If background eviction scheduling is enabled, NDK uses the configured values from `NdkConfig`:

- `cacheEvictionEnabled`
- `cacheEvictionPolicy`
- `cacheEvictionStartupDelay`
- `cacheEvictionInterval`
- `runCacheEvictionOnStartup`

If you enable background eviction scheduling, NDK will periodically run the configured eviction policy while the app is alive.

## Recommended app usage

- Use a persistent `CacheManager` in real apps.
- Read through high-level usecases unless you specifically need low-level control.
- Treat local-first publish as locally durable and eventually relay-delivered.
- Configure eviction for long-lived apps with large caches.
- Let NDK manage decrypted payload caching instead of rewriting encrypted event content yourself.
55 changes: 55 additions & 0 deletions doc/guides/cli/accounts.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,55 @@
---
label: accounts
title: accounts
icon: person
order: 3
---

# `accounts` — manage local identities

Login, persist and switch between Nostr identities. Identities are stored in
plaintext at `~/.ndk/accounts.json` (file mode `0600`) so that subsequent
commands (`broadcast`, etc.) can reuse the active signer. Override the file
location with the `NDK_ACCOUNTS_FILE` environment variable.

> **Security warning**: the accounts file contains signing material (private
> keys or bunker connection secrets) in plaintext. Treat it like an SSH key.
> Do not commit or share it.

```
ndk accounts <sub-command> [args]
```

| Sub-command | Description |
|-------------|-------------|
| `login nsec <hex\|nsec>` | Login with a private key |
| `login npub <hex\|npub>` | Read-only login (public key only, cannot sign) |
| `login bunker <bunkerUrl>` | Connect to a NIP-46 bunker (async; prints an auth URL if the bunker requires one) |
| `login generate [name]` | Generate a fresh keypair, login and persist |
| `logout [pubkey]` | Logout current (or specific) account and remove from store |
| `list` | List persisted accounts |
| `switch <pubkey\|npub>` | Set the active account |
| `whoami` | Show the active account |

```bash
# login with an existing nsec / hex private key
ndk accounts login nsec nsec1...
ndk accounts login nsec 0123456789abcdef0123456789abcdef0123456789abcdef0123456789abcdef

# read-only login (no signing)
ndk accounts login npub npub1...

# connect to a NIP-46 bunker
ndk accounts login bunker "bunker://...?relay=wss://...&secret=..."

# generate a fresh keypair (nsec is printed once + saved to disk)
ndk accounts login generate

ndk accounts list
ndk accounts whoami
ndk accounts switch npub1...
ndk accounts logout
```

The first account logged in becomes the active one. Use `switch` to change
the active account; the choice persists across CLI invocations.
61 changes: 61 additions & 0 deletions doc/guides/cli/blossom.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,61 @@
---
label: blossom
title: blossom
icon: server
order: 7
---

# `blossom` — low-level Blossom operations

Direct access to the Blossom protocol (BUD-*) and the user server list
(kind `10063`). Use this when you need control that
[`files`](files.md) doesn't expose. See the
[blossom usecase](/usecases/blossom.md).

```
ndk blossom <sub-command> [args]
```

| Sub-command | Description |
|-------------|-------------|
| `upload <filePath>` | Upload a local file (hash → upload → mirror, streamed) |
| `download <sha256> <outputPath>` | Download a blob by sha256 to disk |
| `delete <sha256>` | Delete a blob |
| `list [pubkey]` | List blobs for a pubkey (defaults to active account) |
| `mirror <sourceUrl> --server <url>...` | Mirror an existing blob to new server(s) |
| `check <sha256>` | Check blob existence, return an alive URL |
| `servers list [pubkey]` | Show a user's kind `10063` server list |
| `servers publish <url> [url ...]` | Publish your own server list (login required) |

## Options

| Option | Description |
|--------|-------------|
| `--server <url>` (repeatable) | Blossom server(s); defaults to the user server list |
| `--pubkey <hex\|npub>` | Server-list owner |
| `--content-type <mime>` | Override mime type (`upload`) |
| `--media` | Server-side media optimisation (`upload`) |
| `--auth` | Use signed GET (`download`, `check`, `list`) |
| `--since <iso\|unix>` | `list`: only blobs after this date |
| `--until <iso\|unix>` | `list`: only blobs before this date |

## Examples

```bash
# list someone's blossom servers
ndk blossom servers list npub1...

# publish your own server list (order matters — first = most trusted)
ndk accounts login nsec nsec1...
ndk blossom servers publish https://cdn.example.com https://cdn.backup.io

# list all blobs owned by the active account on a specific server
ndk blossom list --server https://cdn.example.com

# mirror an existing blob to a second server
ndk blossom mirror "https://cdn.example.com/<sha256>.pdf" \
--server https://cdn.backup.io

# download a blob straight to disk
ndk blossom download 0123...abcdef ~/blob.bin
```
67 changes: 67 additions & 0 deletions doc/guides/cli/broadcast.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,67 @@
---
label: broadcast
title: broadcast
icon: arrow-up-right
order: 2
---

# `broadcast` — publish events to relays

Publishes one event to the given relays and prints a per-relay result
(`OK`, `REJECTED`, or `NO-RESPONSE`). Accepts three kinds of input:

1. **Pre-signed event JSON** — sent as-is (no signer needed).
2. **Unsigned event JSON** or **inline build** (`--kind` + `--content`) —
signed with `--privkey` or the active `accounts login` identity.
3. **Build a text note inline** — `--kind 1 --content "..."` plus a signer.

```
ndk broadcast [options] <relay> [relay ...]
```

Event source (exactly one required):

| Option | Description |
|--------|-------------|
| `--event <json>` | Inline event JSON |
| `--file <path>` | Read event JSON from file |
| `--stdin` | Read event JSON from stdin (pipe or interactive) |
| `--kind <int>` + `--content <text>` | Build an unsigned text note inline |
| `--pubkey <hex\|npub>` | Pubkey for inline build (defaults to active account) |

Signing:

| Option | Description |
|--------|-------------|
| `--privkey <hex\|nsec>` | Sign with this key (overrides the active account) |
| *(none)* | Use the active account from `ndk accounts login` |

Output control:

| Option | Description |
|--------|-------------|
| `--relay <url>` | Target relay (repeatable; also accepted positionally) |
| `--timeout <sec>` | Per-event broadcast timeout (default `10`) |
| `--consider-done <0..1>` | Fraction of OKs to wait for (default `0.5`) |
| `--no-cache` | Don't save event to local cache |

```bash
# publish a text note using the active account
ndk accounts login nsec nsec1... # one-time
ndk broadcast --kind 1 --content "hello from ndk cli" relay.damus.io nos.lol

# publish a text note with an explicit private key
ndk broadcast --kind 1 --content "hello" --privkey nsec1... relay.damus.io

# broadcast a pre-signed event from a file
ndk broadcast --file signed.json relay.damus.io

# broadcast a pre-signed event piped on stdin
cat signed.json | ndk broadcast --stdin relay.damus.io

# custom OK threshold and timeout
ndk broadcast --event '{...}' --consider-done 1.0 --timeout 20 \
relay.damus.io nos.lol relay.nostr.band
```

Exit code is `0` if at least one relay accepted the event, `1` otherwise.
50 changes: 50 additions & 0 deletions doc/guides/cli/files.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,50 @@
---
label: files
title: files
icon: upload
order: 6
---

# `files` — high-level file management

A convenience wrapper over Blossom that auto-resolves user server lists and
handles sha256-in-URL detection transparently. See the
[files usecase](/usecases/files.md). For lower-level control, use the
[`blossom`](blossom.md) command.

```
ndk files <sub-command> [args]
```

| Sub-command | Description |
|-------------|-------------|
| `upload <filePath>` | Upload a local file, streaming hashing/upload/mirror progress |
| `download <url> <outputPath>` | Download to disk (blossom sha256 URL or plain HTTPS) |
| `delete <sha256>` | Delete a blob from its server(s) |
| `check <url>` | Resolve a URL to a known-alive mirror |

## Options

| Option | Description |
|--------|-------------|
| `--server <url>` (repeatable) | Blossom server(s); defaults to the user server list |
| `--pubkey <hex\|npub>` | Server-list owner (for download/check of someone else's blobs) |
| `--content-type <mime>` | Override mime type (`upload`) |
| `--media` | Server-side media optimisation (`upload`, [BUD-05]) |

## Examples

```bash
# upload to your default blossom servers (requires login for signing)
ndk accounts login nsec nsec1...
ndk files upload ~/photos/cat.jpg

# download a blossom URL to disk
ndk files download "https://cdn.example.com/<sha256>.jpg" ~/out.jpg

# delete by sha256
ndk files delete 0123...abcdef

# check that a URL resolves to an alive server
ndk files check "https://cdn.example.com/<sha256>.jpg" --pubkey npub1...
```
Loading
Loading