# @hashtree/nostr

> Nostr integration for hashtree - ref resolver and event collections

Latest version **0.2.3** (published 2026-09-24) · MIT license · 0 weekly downloads

## Install

```sh
npm install @hashtree/nostr
pnpm add @hashtree/nostr
yarn add @hashtree/nostr
bun add @hashtree/nostr
```

## Health

**Score 70/100 (B)** — status: active.

Positive: has types; esm support; no vulnerabilities; recently updated; high maintenance score; high quality score.

Warnings: low downloads; pre 1.0.

## Facts

| | |
|---|---|
| Version | 0.2.3 |
| Published | 2026-09-24 |
| First published | 2026-01-23 |
| Weekly downloads | 0 |
| License | MIT |
| TypeScript types | bundled |
| Module format | ESM + CommonJS |
| Dependencies | 5 |
| Unpacked size | 343.2 KB |
| Known vulnerabilities | 0 |
| Install scripts | no |
| GitHub stars | 11 |
| Author | Martti Malmi |
| Maintainers | mmalmi |
| Keywords | hashtree, nostr, resolver, events, merkle |

## Links

- npm: https://www.npmjs.com/package/@hashtree/nostr
- Repository: https://github.com/mmalmi/hashtree
- Homepage: https://git.iris.to/#/npub1xdhnr9mrv47kkrn95k6cwecearydeh8e895990n3acntwvmgk2dsdeeycm/hashtree/ts/packages/hashtree-nostr
- Issues: https://git.iris.to/#/npub1xdhnr9mrv47kkrn95k6cwecearydeh8e895990n3acntwvmgk2dsdeeycm/hashtree?tab=issues
- npm.io page: https://npm.io/package/@hashtree/nostr

## Dependencies (5)

- [nostr-tools](https://npm.io/package/nostr-tools.md) ^2.18.2
- [@hashtree/core](https://npm.io/package/@hashtree/core.md) 0.3.2
- [@hashtree/index](https://npm.io/package/@hashtree/index.md) 0.1.14
- [@msgpack/msgpack](https://npm.io/package/@msgpack/msgpack.md) ^3.1.2
- [@hashtree/collection](https://npm.io/package/@hashtree/collection.md) 0.2.10

## Alternatives

- [base64url](https://npm.io/package/base64url.md) — 6.1M weekly downloads
- [get-installed-path](https://npm.io/package/get-installed-path.md) — 502.9K weekly downloads
- [@uppy/url](https://npm.io/package/@uppy/url.md) — 185.8K weekly downloads
- [@d3fc/d3fc-shape](https://npm.io/package/@d3fc/d3fc-shape.md) — 16.2K weekly downloads
- [localizer](https://npm.io/package/localizer.md) — 226 weekly downloads

## Recent versions

- 0.2.3 (latest) — 2026-09-24
- 0.1.14 — 2026-05-18
- 0.1.13 — 2026-05-06
- 0.1.12 — 2026-04-21
- 0.1.11 — 2026-04-20
- 0.1.10 — 2026-04-20
- 0.1.9 — 2026-04-20
- 0.1.8 — 2026-04-12
- 0.1.7 — 2026-04-10
- 0.1.6 — 2026-04-09
- 0.1.5 — 2026-04-08
- 0.1.4 — 2026-04-07
- 0.1.3 — 2026-02-27
- 0.1.1 — 2026-01-23

## README

# @hashtree/nostr

Nostr ref resolving, replaceable publish helpers, signed root snapshots, and
event collections for hashtree.

For app-builder guidance and common pitfalls, see [../../GETTING_STARTED.md](../../GETTING_STARTED.md).

## Install

```bash
npm install @hashtree/nostr
```

## Nostr Event Collections

Use `NostrEventStore` when your app wants a hashtree-native Nostr event collection instead of inventing its own query API.

```typescript
import { MemoryStore } from '@hashtree/core';
import { NostrEventStore } from '@hashtree/nostr';

const store = new MemoryStore();
const events = new NostrEventStore(store);

const profileNotes = await events.query(rootCid, {
  authors: pubkey,
  kinds: [1],
}, { limit: 50 });

for await (const event of events.streamQuery(rootCid, {
  authors: pubkey,
  tags: { t: 'hashtree' },
})) {
  console.log(event.id, event.content);
}
```

`query()` and `streamQuery()` choose the best published index they can (`by-author`, `by-author-kind`, `by-kind`, `by-tag`, or recent) so app code does not need to hand-roll index selection.

## P2P Transport

P2P blob fetching is provided by `@hashtree/fips-transport`. FIPS owns peer
discovery, signaling, and FIPS WebRTC/UDP links; Hashtree carries verified mesh
blob frames over the FIPS node endpoint.

## Nostr Ref Resolver

Resolve `npub/treename` references to merkle root hashes via Nostr events.

### Event Format

Trees are published as **kind 30064** (parameterized replaceable with label). Readers also accept legacy **kind 30078** roots for compatibility:

```
npub1abc.../treename/path/to/file.ext
      │        │           │
      │        │           └── Path within merkle tree (client-side traversal)
      │        └── d-tag value (tree identifier)
      └── Author pubkey (bech32 → hex for event)
```

**Tags:**
| Tag | Purpose |
|-----|---------|
| `d` | Tree name (replaceable event key) |
| `l` | `"hashtree"` label for discovery |
| `hash` | Merkle root SHA256 (64 hex chars) |
| `key` | Decryption key (public trees) |
| `encryptedKey` | XOR'd key (link-visible trees) |
| `selfEncryptedKey` | NIP-44 encrypted (private/link-visible) |

**Visibility:**
- **Public**: plaintext `key` tag
- **Link-visible**: `encryptedKey` + link key in share URL
- **Private**: only `selfEncryptedKey` (owner access)

### Usage

```typescript
import { createNostrRefResolver } from '@hashtree/nostr';

const resolver = createNostrRefResolver({
  subscribe: (filters, onEvent) => { /* your relay client subscribe callback */ },
  publish: (event) => { /* your relay client publish callback */ },
});

const root = await resolver.resolve('npub1.../myfiles');
```

The resolver does not require NDK. Any raw relay client is fine as long as it can subscribe and publish signed events.

### Coalescing Replaceable Publishes

When app code signs replaceable events directly, publishing several updates inside one second can leave relays choosing by event id instead of the last UI state. `createReplaceablePublishQueue()` avoids app-side future timestamps by serializing publishes per replaceable coordinate and only sending the latest queued update in a one-second window.

```typescript
import {
  createReplaceablePublishQueue,
  HASHTREE_ROOT_KIND,
  replaceableEventCoordinateFromTemplate,
} from '@hashtree/nostr';

const publishQueue = createReplaceablePublishQueue();

await publishQueue.publish({
  coordinate: replaceableEventCoordinateFromTemplate(pubkey, {
    kind: HASHTREE_ROOT_KIND,
    tags: [['d', treeName]],
  }),
  publish: async (createdAt) => {
    const signed = await signEvent({
      kind: HASHTREE_ROOT_KIND,
      created_at: createdAt,
      tags: [['d', treeName], ['hash', rootHash]],
      content: '',
    });
    return publishSignedEvent(signed);
  },
});
```

## Signed Tree Snapshots

For immutable permalinks, store a copy of the signed root event as a plain hashtree blob. The snapshot gives you one signed root even when relays do not answer, and you can still watch for newer events later.

For live mutable app data, prefer resolving the current root from relays first. Snapshots are for permalinks, offline reuse, and signed historical captures, not for replacing a live source lookup.

```typescript
import {
  storeTreeEventSnapshot,
  readTreeEventSnapshot,
  fetchLatestTreeEventSnapshot,
  watchLatestTreeEventSnapshot,
} from '@hashtree/nostr';
import { HashTree } from '@hashtree/core';

const hashTree = new HashTree({ store });

const snapshot = await storeTreeEventSnapshot(hashTree, nip19, signedRootEvent);
const sameSnapshot = snapshot
  ? await readTreeEventSnapshot(hashTree, nip19, snapshot.snapshotCid)
  : null;

const latest = await fetchLatestTreeEventSnapshot(
  { snapshotTarget: hashTree, nip19, fetchEvents },
  'npub1...owner',
  'videos/demo',
);

const stop = watchLatestTreeEventSnapshot(
  { snapshotTarget: hashTree, nip19, fetchEvents, subscribeEvents },
  'npub1...owner',
  'videos/demo',
  (nextSnapshot) => {
    console.log(nextSnapshot.snapshotNhash, nextSnapshot.rootCid);
  },
);

// later
stop();
```

The library does not keep a global snapshot cache for you. It provides stateless helpers plus a live watcher; route caching and reuse policy stay with the app. Pass a `HashTree` when you already have one controlling write policy, or a raw `Store` when the default wrapper is enough.

Snapshot routes use the signed snapshot blob `nhash` plus a path and optional link key:

```typescript
import {
  buildTreeEventSnapshotPermalink,
  parseTreeEventSnapshotPermalink,
} from '@hashtree/nostr';

const href = buildTreeEventSnapshotPermalink({
  snapshotNhash: snapshot.snapshotNhash,
  path: ['index.html'],
  linkKey: 'abcd...optional 64-hex link key',
});
// nhash1.../index.html?snapshot=1&k=...

const parsed = parseTreeEventSnapshotPermalink(`htree://${href}`);
```

## License

MIT

---
_Source: https://npm.io/package/@hashtree/nostr · Machine-readable twin of the npm.io package page. Health data is recomputed on every publish._
