npm package discovery and stats viewer.

Discover Tips

  • General search

    [free text search, go nuts!]

  • Package details

    pkg:[package-name]

  • User packages

    @[username]

Sponsor

Optimize Toolset

I’ve always been into building performant and accessible sites, but lately I’ve been taking it extremely seriously. So much so that I’ve been building a tool to help me optimize and monitor the sites that I build to make sure that I’m making an attempt to offer the best experience to those who visit them. If you’re into performant, accessible and SEO friendly sites, you might like it too! You can check it out at Optimize Toolset.

About

Hi, 👋, I’m Ryan Hefner  and I built this site for me, and you! The goal of this site was to provide an easy way for me to check the stats on my npm packages, both for prioritizing issues and updates, and to give me a little kick in the pants to keep up on stuff.

As I was building it, I realized that I was actually using the tool to build the tool, and figured I might as well put this out there and hopefully others will find it to be a fast and useful way to search and browse npm packages as I have.

If you’re interested in other things I’m working on, follow me on Twitter or check out the open source projects I’ve been publishing on GitHub.

I am also working on a Twitter bot for this site to tweet the most popular, newest, random packages from npm. Please follow that account now and it will start sending out packages soon–ish.

Open Software & Tools

This site wouldn’t be possible without the immense generosity and tireless efforts from the people who make contributions to the world and share their work via open source initiatives. Thank you 🙏

© 2026 – Pkg Stats / Ryan Hefner

@dignetwork/dig-urn-resolver

v0.5.1

Published

Resolve a DIG URN to its data through the protocol (node-first ladder, verified + decrypted). Works in the browser AND Node.js.

Downloads

1,227

Readme

dig-urn-resolver

Resolve a DIG URN to its data through the protocol, node-first. A Rust crate and a @dignetwork/dig-urn-resolver wasm/npm package. First consumer: Sage wallet NFT images.

Positioning — the canonical client-side URN resolver

This is THE canonical, project-wide client-side URN→data resolver (the #668 convergence: hub, the extension, dig-sdk, dig-dns and other consumers converge on it). It sits strictly upstream of dig-node: it is a client that talks to a dig-node over the wire (the node /s/ + /health surface, else the rpc.dig.net gateway) — dig-node does all the heavy lifting (store sync, serve, decrypt, chain anchoring) and never depends on this crate. Consume it from Rust (dig-urn-resolver) or JS/wasm (@dignetwork/dig-urn-resolver); do not reimplement URN resolution elsewhere.

Given a URN of the form

urn:dig:chia:<store_id>[:<root>]/<resource_key>[?salt=<hex>]

it returns the resource's bytes + content type, following the canonical §5.3 ladder — explicit override > dig.local > localhost:9778 > rpc.dig.net — using the first tier that responds:

  • node tierGET /s/<storeId>[:<root>]/<path>: a local dig-node decrypts + verifies server-side under a loopback trust boundary and returns plaintext.
  • rpc tierdig.getContent: a blind fetch of opaque ciphertext + inclusion proofs from the untrusted public gateway, verified against the chain-anchored root and decrypted client-side (fail-closed).

All read-crypto (URN canonicalization + retrieval-key derivation, merkle inclusion verify, AES-256-GCM-SIV open) is reused verbatim from digstore-core — the same functions the browser read-crypto and the on-chain crates share — so this crate can never skew from the canonical crypto.

Security invariants

  • Node trust is loopback-only. The crypto-free node /s/ path is trusted ONLY for an asserted-loopback host (127.0.0.0/8 / ::1 / localhost, or dig.local iff it resolves to loopback) — it is the user's own machine. ANY other host, including an explicit override pointed at a remote host, uses the client-VERIFIED rpc path. On the node path the response MUST carry X-Dig-Verified: true, else the bytes are rejected fail-closed. (Defeats a remote/override host and a LAN mDNS spoof of .local serving unverified bytes as trusted.)
  • The rpc trust root comes from the URN, not the gateway. Over the untrusted gateway only a root-pinned URN is accepted; a rootless URN is rejected (RootRequired) because its root would otherwise come from the same gateway serving the content. (Rootless is fine over a loopback node — the local node is the trust anchor. Root-pinned + private/salted stores are unaffected.)

Three outcomes, never conflated

| Outcome | Meaning | Bytes | |---|---|---| | Success | verified, decrypted content | the real content | | IntegrityFailure | bytes fetched but failed merkle/decrypt verify (tampered / decoy / wrong root) — a hard, fail-closed security failure | never the unverified bytes; renders a branded "Integrity Verification Failed" page | | Unreachable | every tier down; nothing fetched — friendly + retryable | a branded "DIG Network unreachable" + Connect-to-Node page |

A malformed URN, a not-found resource, and a reachable rpc protocol error are hard errors (ResolveError).

Rust

use dig_urn_resolver::{native, ResolveOutcome};

#[tokio::main]
async fn main() {
    match native::resolve("urn:dig:chia:<store>:<root>/img/logo.png").await.unwrap() {
        ResolveOutcome::Success(data) => { /* data.bytes, data.content_type */ }
        ResolveOutcome::IntegrityFailure => { /* security failure — do not show */ }
        ResolveOutcome::Unreachable => { /* network down — offer "connect a node" */ }
    }
}

Inject a custom transport (or an explicit endpoint) via Resolver::with_options.

Browser / Sage (@dignetwork/dig-urn-resolver)

The front-door API is the branded, well-typed DigNetwork client:

import init, { DigNetwork } from "@dignetwork/dig-urn-resolver";
await init();

const dig = new DigNetwork();                         // all defaults
// or pass an options object — set only what you need, no undefined placeholders:
// const dig = new DigNetwork({ endpoint, connectUrl, cachePath });

// The NFT-image case — a URL for <img src>, working with no dig-node running:
img.src = await dig.resolveImageUrl(nftDataUri);

// Or the typed form (real outcome + MIME, not an image):
const { outcome, bytes, contentType } = await dig.resolve(nftDataUri);
// outcome ∈ "success" | "integrity_failure" | "unreachable"

resolveImageUrl ALWAYS returns a usable <img> URL and never throws for a normal failure: the real verified image on success, else a branded DIG error image (embedded PNG data: URI) matching the failure — integrity failure, network unreachable, not found, invalid URN, or a generic error. On an integrity failure it is the STATIC branded placeholder — never the unverified bytes as the image. (The HTML error pages via resolve().render() stay for the webview/navigation case.) Low-level free functions (resolve, resolveObjectUrl) remain available.

Caching

URNs are content-addressed → immutable → cacheable, so results are cached as an additive layer in front of resolve that never weakens fail-closed:

  • Only VERIFIED Success bytes are cached — never an integrity/unreachable/ not-found failure (a cached failure would block recovery).
  • Keyed by the content-addressed identity storeId:root:resourceKey:salt with the CONCRETE resolved root — a root-pinned URN is immutable; a rootless URN is cached under the root the resolve produced (X-Dig-Root), never a stale URN→bytes mapping.
  • Memory (default, bounded LRU): process-trusted (only holds what this process verified this run) — a hit skips re-verification.
  • Disk (optional cachePath; native Rust AND Node.js): UNTRUSTED — it stores the verifiable artifacts (ciphertext + proof + chunk lengths), and a disk hit is re-verified against the URN's root before use, so a tampered on-disk file FAILS verification → IntegrityFailure (never served). Filenames are SHA-256(identity) (no path-traversal). Ignored clientside in the browser (no filesystem); the memory cache still applies there.

Persistent disk caching is a native (Rust) capability — pass a cache_path in ResolveOptions, and verified results survive across process runs (a long-lived on-disk cache under the given directory). Because URNs are immutable + content- addressed, a later run re-serves a cached asset without a network round-trip, and every disk hit is still re-verified against the URN's root before use:

use dig_urn_resolver::{native, ResolveOptions, ResolveOutcome};

#[tokio::main]
async fn main() {
    let options = ResolveOptions {
        cache_path: Some("/var/cache/dig-urn".into()), // long-lived disk cache
        ..Default::default()
    };
    match native::resolve_with("urn:dig:chia:<store>:<root>/img/logo.png", options)
        .await
        .unwrap()
    {
        ResolveOutcome::Success(data) => { /* served from disk on a later run */ }
        ResolveOutcome::IntegrityFailure => { /* re-verify failed — never served */ }
        ResolveOutcome::Unreachable => { /* network down — offer "connect a node" */ }
    }
}

In the @dignetwork/dig-urn-resolver wasm package the DigNetwork constructor takes an options object with the same cachePath field, and it is fully functional under Node.js — the Node build injects Node's fs, so verified results persist to the given directory and are re-verified on read, exactly like the native crate:

// Node.js service (CommonJS) — a persistent, cross-run disk cache
const { DigNetwork } = require("@dignetwork/dig-urn-resolver");

// Set ONLY the field you need — no undefined placeholders.
const dig = new DigNetwork({ cachePath: "/var/cache/dig-urn" });

// First run fetches + verifies; a later run (same cachePath) re-serves from disk.
const img = await dig.resolveImageUrl("urn:dig:chia:<store>:<root>/img/logo.png");

The options object accepts { endpoint?, connectUrl?, cachePath? } (all optional); the generated TypeScript exports a DigNetworkOptions interface with those named fields.

Clientside (browser) cachePath is a harmless no-op — a browser has no filesystem, so the wasm build never wires fs there and the bounded in-memory cache applies instead (a same-process hit still skips re-verification). The persistent disk cache is available to native Rust consumers AND to the Node.js build; only the browser falls back to memory-only.

Runs in the browser AND Node.js

The @dignetwork/dig-urn-resolver package is dual-target — one package that imports cleanly in both:

// Browser bundler (Vite / webpack) — ESM
import init, { DigNetwork } from "@dignetwork/dig-urn-resolver";
await init();

// Node.js service — CommonJS
const { DigNetwork } = require("@dignetwork/dig-urn-resolver");

The branded DigNetwork API, the three fail-closed outcomes, and the MIME are IDENTICAL in both; only env-specific plumbing differs, and it degrades gracefully — never a hard-fail in either environment:

  • Ladder — the dig.local/localhost /health probe works in Node; in a browser it's often CORS-blocked. The probe is caught/timed-out, so the ladder simply falls through to the verified rpc.dig.net tier (never to unverified bytes).
  • Cache — the disk cachePath works natively AND under Node.js (the Node build injects fs); in a browser there is no filesystem, so cachePath is a harmless no-op and the bounded in-memory cache applies.
  • Image URLresolveImageUrl returns a blob: URL where URL.createObjectURL exists (browser) and a data: URL otherwise (Node.js) — always usable, never throws.

Content type & the default view

contentType is present on every resolve result: the store's stored Content-Type on the node path, else inferred from the URN path extension / magic bytes. A bare-store or trailing-slash URN resolves to the §8.5 default view index.html.

CORS note for consuming apps (Sage/Tauri)

The /health + /s/ probes hit dig.local/localhost from the app origin and may be CORS-blocked in a desktop webview; the rpc.dig.net fallback (Access-Control- Allow-Origin: *) always works, so a resolve succeeds node-absent. Add the endpoints to the app's connect-src CSP. For the browser node path to read the verification attestation, the node must send Access-Control-Expose-Headers: X-Dig-Verified (a missing header fails closed) — see #669.

Example

cargo run --example sage_nft_image

Resolves a root-pinned NFT-image URN to displayable bytes with no dig-node running (rpc fallback), the exact byte path resolveObjectUrl wraps for <img src>.

License

GPL-2.0-only (inherited from the reused digstore-core read-crypto).