did-btcr2-js

@did-btcr2/api

High-level SDK facade for the did:btcr2 DID method. Wraps @did-btcr2/method and the surrounding crypto / bitcoin / key-management packages behind a single ergonomic entry point.

Part of the did-btcr2-js monorepo.

Summary

The lower-level packages (@did-btcr2/method, @did-btcr2/cryptosuite, @did-btcr2/key-manager, @did-btcr2/bitcoin) are designed to be composable and sans-I/O. This package is the thin layer above them: it owns Bitcoin endpoint configuration, CAS retrieval, key management, and the dispatch loop for the sans-I/O state machines.

If you’re integrating did:btcr2 into an app, start here. If you’re customizing the protocol, drop down to @did-btcr2/method directly.

The api wires the configured BitcoinApi into the sans-I/O Resolver and Updater state machines, fulfilling NeedBeaconSignals, NeedFunding, NeedBroadcast, and CAS-related needs (NeedGenesisDocument, NeedCASAnnouncement, NeedSignedUpdate) automatically. NeedSMTProof is not auto-fulfilled by the facade: SMT proofs are nonce-blinded (there is no content address to fetch them by), so they must be provided upfront via options.sidecar.smtProofs; resolution fails fast with that pointer otherwise. Multi-party aggregation is out of scope here; drive the Updater directly and hand NeedBroadcast to the aggregation runner from @did-btcr2/aggregation.

On the write path, publishToCas ('never' 'auto' 'always', default 'never') controls whether update artifacts are published to the configured CAS before the on-chain broadcast. CAS publication is optional and never required: every update, for every beacon type, completes and is distributable via sidecar regardless. Publishing is opt-in: pass 'auto' (best-effort - publishes when a writable CAS is configured, otherwise skips silently and never blocks the update) or 'always' (requires a writable CAS and throws up-front when none is available). When publication happens, the canonical signed update (all beacon types) plus the CAS Announcement (CAS beacons) reach the CAS, so resolvers can fetch every OP_RETURN update hash from the CAS with no sidecar. Update calls return a DidUpdateResult carrying the signal txid and the per-beacon-type sidecar artifacts (announcement, SMT proof).

Install

npm install @did-btcr2/api

Or with pnpm:

pnpm add @did-btcr2/api

Runtime note: ESM-first package; a CJS build ships via the require export condition (some transitive deps are ESM-only, so import is the reliable path). Ships a browser bundle at dist/browser.mjs for bundler-based environments. Requires Node >= 22.

Key Exports

Concern Entry point
Main facade DidBtcr2Api, createApi(config?)
Sub-facades BitcoinApi, CasApi, CryptoApi, DidApi, KeyManagerApi, DidMethodApi
Fluent update UpdateBuilder (from api.btcr2.buildUpdate(...))
Config types ApiConfig, BitcoinApiConfig, CasConfig, Logger
Resolution result ResolutionResult (tryResolveDid return type)
Re-exports from method/common DidDocument, DidDocumentBuilder, Identifier, IdentifierTypes

Quick Start

Generate a DID and resolve it

import { createApi } from '@did-btcr2/api';

const api = createApi({ btc: { network: 'mutinynet' } });

// Generate keypair, derive DID, import the secret into the in-process KMS.
const { did, keyId } = api.generateDid({ network: 'mutinynet' });

// Resolve. Bitcoin signals are fetched automatically via the configured BitcoinApi.
const resolution = await api.resolveDid(did);
console.log(resolution.didDocument?.id);

Update via the fluent builder

import { LocalSigner } from '@did-btcr2/keypair';

// Ids are matched exactly against the document: use full DID URLs
// (e.g. `${did}#initialKey`), not bare fragments.
const { signedUpdate, txid, announcement, publishedToCas } = await api.btcr2
  .buildUpdate(currentDoc)
  .patch({ op: 'add', path: '/service/-', value: newService })
  .version(2)
  .verificationMethodId(`${did}#initialKey`)
  .beacon(currentDoc.service[0].id)
  .signer(new LocalSigner(secretKey))
  .execute();

Publish update artifacts to a CAS before broadcasting

// A writable CAS (an IPFS node's RPC endpoint) makes updates resolvable
// without sidecar data: the signed update (and, for CAS beacons, the
// announcement) is published before the beacon transaction is broadcast.
const api = createApi({
  btc : { network: 'mutinynet' },
  cas : { rpcUrl: 'http://127.0.0.1:5001' },
});

const result = await api.updateDid({
  did,
  patches              : [{ op: 'add', path: '/service/-', value: newService }],
  verificationMethodId : `${did}#initialKey`,
  beaconId             : `${did}#initialP2WPKH`,
  signer,
  // publishToCas defaults to 'never' (opt-in): update artifacts are returned
  // for sidecar distribution and nothing is published. Opt in with 'auto' to
  // publish to the writable CAS configured above. Note 'auto'/'always' publish
  // canonical signed updates to the configured (possibly public) CAS before the
  // on-chain anchor, so keep the 'never' default for sidecar-only privacy.
  publishToCas         : 'auto',
});
console.log(result.txid, result.publishedToCas); // e.g. { update: true, announcement: false }

Resolve without throwing

const result = await api.tryResolveDid(did);
if (result.ok) {
  console.log(result.document);
} else {
  console.warn(`resolve failed: ${result.error} - ${result.errorMessage}`);
}

Sign with a KMS-backed signer (HSM / cloud / external keystore)

import { KeyManagerSigner } from '@did-btcr2/key-manager';

const signer = new KeyManagerSigner(api.kms.kms, keyId);

await api.updateDid({
  did,
  patches              : [{ op: 'add', path: '/service/-', value: newService }],
  verificationMethodId : `${did}#initialKey`,
  beaconId             : `${did}#initialP2WPKH`,
  signer,
});

Architecture Principles

Build & Test

# From packages/api/
pnpm build              # Compile ESM + browser bundle + type declarations
pnpm build:tests        # Compile tests to tests/compiled/
pnpm test               # Run the test suite with coverage
pnpm lint               # ESLint (zero warnings tolerated)

The lib/ directory contains end-to-end scripts that exercise the full update path against regtest, mutinynet, signet, testnet3, and testnet4. Run with bun packages/api/lib/e2e-*.ts or tsx. On non-regtest networks the scripts persist generated secret keys to lib/.e2e-keys/ (gitignored) so funds at beacon addresses can be recovered.

Documentation

License

MPL-2.0