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

@midnight-ntwrk/midnight-did-api

v0.7.0

Published

Programmatic API for creating, updating, deactivating, and resolving Midnight DIDs.

Readme

@midnight-ntwrk/midnight-did-api

Programmatic API for creating, updating, deactivating, and resolving Midnight DIDs.

Responsibilities

  • Build/connect providers (node, indexer, proof server)
  • Submit contract circuits for DID operations
  • Map inputs/outputs between app/domain and ledger/runtime
  • Generate and persist DID controller private state for create/rotation flows
  • Provide integration test topology and helpers
  • Return DID resolution data (didDocument, didDocumentMetadata) for API callers

Use It When

  • you need programmatic DID deployment or mutation flows
  • you need provider bootstrap for standalone, preprod, or env-driven mainnet
  • you are building a higher-level application and do not want to manage raw contract/runtime wiring

Architecture

graph TD
  App[Manager / Tests / Integrator]
  API[API facade]
  Domain[Domain validation]
  DidPkg[DID mapper]
  Contract[Contract bindings]
  Providers[Providers]
  Chain[(Midnight chain)]
  Indexer[(Indexer)]

  App --> API
  API --> Domain
  API --> DidPkg
  API --> Contract
  API --> Providers
  Providers --> Chain
  Providers --> Indexer

Update Sequence

sequenceDiagram
  participant Caller
  participant API
  participant Contract
  participant Indexer

  Caller->>API: addService / addVerificationMethod / ...
  API->>API: validate + normalize
  API->>API: sign controller authorization digest for current contract version
  API->>Contract: submit circuit tx + controller signature
  Contract-->>API: accepted tx
  API->>Indexer: fetch current state
  API-->>Caller: updated DID state or DID Resolution Result

State Model

API enforces lifecycle rules around:

  • active DID: allows updates
  • deactivated DID: mutating operations rejected
  • controller authorization: signs a domain-separated digest containing contract id, current version, operation name, and operation arguments before each controller-gated mutation
  • controller rotation: generates a new wallet-local secret, persists it in a pending slot, derives the next controller public key locally, and submits the rotation circuit with a current-version controller signature. The pending secret is promoted and cleared only after finalized transaction data returns; ambiguous submission/finality failures retain it for ledger reconciliation
  • controller recovery: a dedicated recoveryAuthorityPublicKey can authorize recoverControllerKey to rotate the active controller key; ordinary controller-gated operations require only the active controller secret, while recovery requires the matching recovery secret

(Exact schema/canonicalization rules live in domain.)

Explicit Verification-Method Removal

removeVerificationMethod and removeSchnorrJubjubVerificationMethod each submit at most one removal circuit call. They never remove DID verification relationships implicitly. If the method is still referenced, the API rejects before signing or submission with VerificationMethodReferencedError:

  • code is verification_method_referenced;
  • methodId is the selected physical ledger identifier;
  • relations lists current references in canonical DID relation order.

Applications choose the cleanup order by calling removeVerificationMethodRelation once per relationship and then calling the method-removal helper. These independently finalized transactions are not atomic. After an ambiguous or partial failure, re-read ledger/DID state, skip operations already reflected on-chain, and submit only the outstanding steps. Removing an absent relationship remains an explicit error rather than an idempotent no-op. The Compact removal circuits independently reject referenced methods, so API preflight is useful typed feedback but not the authority for direct callers or concurrent updates.

Finalized Deployment With Incomplete Private-State Setup

@midnight-ntwrk/midnight-js-contracts 4.0.2 performs more work after the ledger reports deployment success but before deployContract returns: submitDeployTx synchronously calls setContractAddress(target), then awaits set(initialPrivateState) and setSigningKey(target, signingKey). A wrapper around only the returned promise cannot distinguish rejection of those post-finality writes from a pre-success deployment failure, and reserving the target after return is too late.

deploy therefore passes a deployment-scoped private-state-provider interceptor to deployContract. On the dependency's synchronous target-binding call it canonicalizes and reserves the target under the already-held source lease before delegating the provider mutation. All other provider methods retain their original receiver. The source and target reservations remain held until the entire dependency call settles; there is no elapsed-time lease expiry, because stale dependency work could otherwise mutate state after a competitor takes the same target. The captured interceptor is deactivated when that call settles and cannot spend the released lease later.

If target reservation, active-state persistence, signing-key persistence, or returned-handle construction then fails, deploy/createDID throws DIDContractDeploymentFinalizedPrivateStateIncompleteError with only a stable code/name, canonical contractAddress, and interceptor-controlled setupStage: target_reservation, private_state_persistence, signing_key_persistence, or contract_handle_construction. The address is evidence that the dependency reported ledger success by calling setContractAddress; the stage identifies the local step that did not complete. The handle-construction stage begins after both writes complete and includes the second address bind performed while deployContract builds callTx; that repeat bind never resets the stage to reservation or persistence. The error deliberately discards the source error, deployed-contract handle, transaction/finality data, and all public or private deployment data. It never uses cause, copies provider fields, or includes an arbitrary provider name/message. Every rejection before the target is observed, including primitive rejection values, adversarial error objects, and a genuine DeployTxFailedError, is preserved unchanged. On success the dependency has already bound the provider and persisted both values, so the API performs no second bind or active-state write that could overwrite a concurrently rotated controller key.

Do not retry deployment blindly. Retain the original private state separately and reconcile provider and ledger state by the error's canonical address. Use the stage to determine which local setup step needs verification; any write that rejected can have an uncertain disposition. Resolve a competing binding owner and join the finalized address using state that matches the current ledger controller rather than overwriting its namespace. Inspect restricted external provider logs separately when diagnostics are needed. Never log the source error through this typed error or attach it to the typed error before propagating it. Future diagnostics at this boundary must be explicitly allowlisted and redacted; they must not retain or traverse the provider error graph.

Controller Secret Recovery Posture

The API package can initialize, persist, rotate, recover, and restore private state that authorizes DID updates. It cannot recover secrets from ledger state, but it can submit recoverControllerKey when private state contains, or the caller explicitly supplies, the recoverySecretKey matching the on-ledger recoveryAuthorityPublicKey. Explicitly supplied recovery secrets are used for that recovery call and are not newly persisted into active private state, though an already stored recovery secret that matches the on-ledger recovery authority is preserved when the new controller secret is promoted.

If rotation or recovery throws after callTx is invoked without returning finalized transaction data, the API retains the pending replacement secret because receipt loss cannot prove that the on-chain operation failed. After connectivity is restored, obtain a trusted finalized read of the on-ledger controllerPublicKey before retrying; reconnection or the first available read alone is not proof of non-finalization. A failure definitely before callTx invocation instead attempts to remove the newly created candidate while the same lease is held. If that cleanup rejects, its disposition is unknown; the warning says the record may remain or may already have been removed and keeps explicit discard guidance for a retained record. If the replacement public key is the finalized current key, verify that the retained secret derives that key and promote it with recoverPendingControllerPrivateState(providers, { contractAddress, rotationFinalized: true }). The rotationFinalized option is an explicit caller assertion; these helpers do not query ledger state or verify finality. getMidnightDIDLedgerState returns the state supplied by the configured public data provider without adding a finality or freshness guarantee, so the application must obtain provider-specific authoritative evidence before making that assertion. If authoritative reconciliation confirms that the operation did not finalize, discard the candidate explicitly with discardPendingControllerPrivateState(providers, { contractAddress, rotationFinalized: false }) before starting another attempt. Until then, retain it even if an available read still shows the old public key. That explicit assertion also permits removal of a malformed non-null pending record, avoiding a persistent lockout; an absent record still throws PendingControllerPrivateStateUnavailableError. Promotion requires a valid pending controller state, so a missing or malformed record throws the same stable typed error without writing active state or removing the record. If promotion succeeds but pending cleanup rejects, the helper warns and returns the promoted state, but cannot confirm the cleanup disposition: the pending record may remain or may already have been removed. A later reconciliation either processes retained state or throws PendingControllerPrivateStateUnavailableError if deletion committed.

A new attempt made after any non-null candidate is persisted fails with PendingControllerPrivateStateExistsError. A rotation, recovery, promotion, or discard racing an in-flight pending-state lifecycle fails with PendingControllerPrivateStateBusyError; neither error can replace, promote, or remove that operation's candidate. Rotation, recovery, and reconciliation bind an untracked provider to the operation's canonical DID address and reject a known different API-tracked binding with PrivateStateProviderContractMismatchError before provider or ledger access.

Public rotation and recovery auto-bind or assert the canonical contract address; public promotion/discard reconciliation requires contractAddress. The supported baseline assumes that one application process is the writer for a given DID. Calls through API-bound wrappers in that process share a process-local critical section from preflight through pending persistence, transaction settlement, promotion, and cleanup. Reservation acquisition is fail-fast: competing rotation, recovery, or reconciliation immediately throws PendingControllerPrivateStateBusyError, even if the owner hangs. The owner remains busy until underlying work is cancelled and its operation settles, the operation otherwise terminates or settles, or the process exits. There is deliberately no lease expiry: stale provider or transaction work could complete later and overwrite, promote, or remove state owned by another operation. After cancellation, termination, or process restart, reconcile trusted finalized ledger state and private state before another mutation.

Provider-object fallback is only for internal/deep unbound use. Direct setContractAddress or storage mutation and independently unbound wrappers are outside this guarantee. Multiple application processes intentionally writing the same DID are outside the supported baseline and must use a distributed lock or equivalent fencing mechanism; the API does not provide one. joinContract acquires the same fail-fast owner-token lease before binding and holds both its source and target reservations through private-state loading and findDeployedContract; competing source/target controller lifecycle or binding calls fail busy before mutation. Join failure releases only its owned keys. The provider exposes no atomic conditional write across processes. Its unbound-state exception is ignored only when it exactly matches the upstream message; decorated I/O and other provider failures propagate. See discussion #440 for the architecture decision and future multi-writer considerations.

Applications should back up controller and recovery private state alongside their wallet backup material, protect it with custody controls appropriate for production signing keys, and test restore/recovery flows before relying on a DID. Private state created before the recovery-authority contract surface contains no recovery secret; it can still authorize ordinary controller-gated operations when paired with a compatible contract, but it cannot submit recoverControllerKey unless the recovery secret is imported or supplied explicitly. If both the active controller secret and recovery secret are lost, the DID remains publicly resolvable but cannot be updated, rotated, recovered, or deactivated by this method version. Organizational operators that need multi-person approval or social recovery should implement that policy before the API call that signs a controller or recovery authorization; the contract still receives one signature for the selected operation.

Resolution Responses

The API package exposes both convenience and DID Core envelope helpers. resolve returns the ledger-derived DID Document and DID Document metadata, or null when the contract state is missing. resolveDIDResolutionResult returns the full DID Core Resolution Result envelope with didResolutionMetadata. Successful abstract resolve responses must not set didResolutionMetadata.contentType; that field is reserved for resolveRepresentation responses where the body is a DID Document byte stream.

The API package also exports resolveRepresentation(providers, didContract, options). It delegates to the shared MidnightDIDResolver and returns didDocumentStream as a Uint8Array | null (null on resolution errors), didDocumentMetadata, and didResolutionMetadata. This is the package boundary intended for midnight-did-resolver: the downstream service owns HTTP routing and status codes, while this package owns ledger access, representation selection, and DID resolution errors.

Build & Test

  • Build: pnpm --filter ./packages/api build
  • Typecheck examples: pnpm --filter ./packages/api typecheck:examples
  • API import discipline: pnpm run check:api-source-imports
  • DID package import discipline: pnpm run check:source-imports
  • Unit tests: pnpm --filter ./packages/api test
  • Integration tests: pnpm --filter ./packages/api test-api

API TypeScript source and tests use explicit .js or .json extensions for relative imports, including Vitest mocks. This keeps the package aligned with the emitted ESM graph and avoids resolver-only test behavior. The wider check:source-imports guard applies the same rule to all DID-owned TypeScript package sources outside generated src/managed artifacts.

Runtime Profiles

  • StandaloneConfig
  • TestnetLocalConfig
  • TestnetRemoteConfig
  • PreprodConfig
  • MainnetConfig
  • ProfileConfig

Defaults:

  • all profile defaults live in src/config-profiles.ts
  • PreprodConfig and MainnetConfig use public indexer v4 endpoints (/api/v4/graphql + /ws).
  • MainnetConfig defaults to local proof server (http://127.0.0.1:6300) so it can be used with local proving while targeting mainnet indexer/node.
  • constructing any profile config calls setNetworkId() through applyMidnightNetworkProfile(), so wallet and contract operations see the correct Midnight network before they start.

The docs site publishes the generated endpoint matrix at https://midnightntwrk.github.io/midnight-did/guide/network-endpoints; it is generated from src/config-profiles.ts during docs preparation and validation.

You can still override MainnetConfig endpoints explicitly when needed. New tooling should use ProfileConfig when the profile name is data-driven rather than hard-coded in a class constructor. Every ProfileConfig instance exposes the resolved profileName so logs and operator tooling can report the active profile without inferring it from URLs.

Network Mapping Helpers

Use the typed mapping helpers when converting between Midnight runtime network ids and DID-domain network names:

  • RuntimeToDomain.NetworkMap: maps runtime NetworkId values to DID-domain MidnightNetwork values.
  • DomainToRuntime.NetworkMap: maps DID-domain MidnightNetwork values back to runtime NetworkId values.
  • RuntimeToDomainNetworkMap and DomainToRuntimeNetworkMap: readonly public type aliases exported from the package barrel for downstream configuration and test helpers.

The older NetworkMapping export is a compatibility alias for RuntimeToDomain.NetworkMap. New code should prefer the direction-specific helpers so map intent is visible at the call site.

Provider adapters for proof, indexer, and ZK configuration are loaded lazily by configureProviders(). Importing the API package barrel for mapping helpers, types, or examples does not load those runtime adapters.

Release Artifact Metadata

The package embeds ZK artifact locations for its own published version:

import {
  MIDNIGHT_DID_API_VERSION,
  createMidnightDidZkArtifactLocations,
} from "@midnight-ntwrk/midnight-did-api";

const locations = createMidnightDidZkArtifactLocations(
  MIDNIGHT_DID_API_VERSION,
);

Use locations.ghcr.reference to pull the matching GHCR OCI artifact in Node or CI tooling. RC and final release versions also include locations.githubRelease.archiveUrl; snapshot versions publish workflow artifacts and GHCR artifacts only, so locations.githubRelease is null.

Node consumers can download, verify, and unpack GitHub Release assets directly:

import {
  downloadMidnightDidGithubReleaseZkArtifacts,
  MIDNIGHT_DID_API_VERSION,
} from "@midnight-ntwrk/midnight-did-api";

const bundle = await downloadMidnightDidGithubReleaseZkArtifacts({
  version: MIDNIGHT_DID_API_VERSION,
  outputDir: ".midnight-did-zk",
});

process.env.MIDNIGHT_DID_ZK_CONFIG_PATH = bundle.zkConfigPath;

bundle.zkConfigPath is the directory to pass to NodeZkConfigProvider or to expose from an HTTP server for FetchZkConfigProvider. The helper verifies the release .sha256 file, checks that the downloaded manifest matches the embedded archive manifest, and validates every circuit file checksum before returning. When outputDir, tempDir, or pullDir are omitted, helper-created directories are retained because bundle.archivePath and bundle.zkConfigPath point into them. Callers that need deterministic cleanup should pass explicit directories and remove them after the ZK provider no longer needs the files. For GHCR OCI artifacts, use pullMidnightDidGhcrZkArtifacts() in an environment with the oras CLI available.

When the ZK bundle is unpacked outside the installed package, set MIDNIGHT_DID_ZK_CONFIG_PATH to the directory containing manifest.json, keys/, and zkir/ before importing @midnight-ntwrk/midnight-did-api. Without this override, the API uses the managed artifacts bundled with the installed contract package when available.

setLogger() is optional for embedders. Until it is called, API helpers use a no-op logger so wallet/provider setup can run in minimal scripts without preconfiguring logging.

Main Source Files

  • src/index.ts
  • src/lib.ts public compatibility facade
  • src/config-profiles.ts network profile catalog and network-id application
  • src/deploy.ts contract deployment, join, and private-state initialization
  • src/providers.ts provider composition for DID runtime dependencies
  • src/private-state-storage.ts private-state storage account/password wiring
  • src/transaction-intents.ts manual unshielded intent signing workaround
  • src/wallet-context.ts SDK wallet construction and restore context assembly
  • src/wallet-dust.ts dust-registration workflow helper
  • src/wallet-provider.ts wallet facade to Midnight wallet/provider adapter
  • src/wallet-state.ts wallet snapshot, sync, balance, and funding wait helpers
  • src/wallet.ts wallet construction and restore facade
  • src/wallet-keys.ts seed parsing, HD key derivation, and unshielded address helpers
  • src/wallet-sdk-config.ts shared wallet SDK configuration builders
  • src/lightweight.ts stateless crypto helpers only; wallet wait behavior lives in src/wallet-state.ts
  • src/did-subject.ts DID subject and bound fragment normalization
  • src/ledger-mappers.ts DID document domain-to-ledger DTO mapping helpers
  • src/update.ts DID document update, deactivate, and resolve orchestration
  • src/types.ts
  • src/test/

Type-safety policy:

  • production API source must not use as any casts
  • as unknown as casts must be explicitly allowlisted in pnpm run check:did-surface-discipline
  • keep SDK type mismatches localized behind narrow adapter helpers and update the surface-discipline guard when an intentional compatibility escape hatch is unavoidable

Legacy deep source files src/contract-lifecycle.ts and src/did-operations.ts remain as short-lived deprecation shims for external deep source-path imports. Internal code should use the split modules above, and package consumers should import from @midnight-ntwrk/midnight-did-api.

Deploy And Update Example

See examples/README.md, examples/deploy-did.ts, and examples/update-did.ts for package-local deploy/update flows that use only API package exports. Resolver services, DID manager UI, and reusable secret storage stay in midnight-did-resolver.

Integration Teardown

packages/api/src/test/commons.ts now uses:

  • unique compose project names per run
  • env.down({ removeVolumes: true })
  • fallback docker compose down --volumes --remove-orphans

This reduces container/volume leaks when tests fail mid-run.