did-btcr2-js

ADR 006: API Package Boundary

Status: Accepted

Date: 2025-09-26

Commit: 74b517e

Context

After the Bitcoin package was extracted (see ADR 005), the package graph had common, keypair, cryptosuite, bitcoin, and method as layer-one packages. Every consumer writing real code against the library had to import from four or five of them at once:

import { DidBtcr2, Identifier } from '@did-btcr2/method';
import { SchnorrKeyPair } from '@did-btcr2/keypair';
import { SchnorrMultikey } from '@did-btcr2/cryptosuite';
import { BitcoinRestClient } from '@did-btcr2/bitcoin';
import type { KeyBytes, PatchOperation } from '@did-btcr2/common';

This is correct at the layer level: each package owns a focused concern: but it pushes the assembly cost onto every consumer. Three problems surfaced:

  1. Cross-package configuration was ad hoc. A consumer wiring up a real resolution needed to construct a Bitcoin REST client, maybe an RPC client, decide default confirmation counts, pick a network, and then hand all of that to method. Each package had its own config shape; the consumer was the integration layer.
  2. The “how do I use this” surface was too wide. New consumers had to learn the entire layer graph before writing their first resolve() call. Tutorials and examples had to pull in five packages to show anything interesting.
  3. There was no stable seam for cross-cutting concerns. Things like input validation, dispose lifecycle, default logger, branded types, timeouts: none of these belonged to any single layer-one package, but all of them were reasonable things a consumer would want in one place.

The method package was already the “top of the stack” in the DID-protocol sense, but elevating it to “the SDK” would have conflated two different concerns: the spec implementation and the consumer ergonomics layer. Those two roles have different change drivers. Spec changes force method to change; consumer-ergonomics improvements shouldn’t.

Options considered

  1. No facade. Consumers assemble from the layer packages directly. Lowest infrastructure cost; maximum consumer burden. Every example in docs pulls from 4-5 packages; every consumer writes its own integration glue.
  2. Re-export from method. Treat method as the top-level entry, re-exporting from the layers below. Single import for consumers; method becomes a god package that owns both spec implementation and consumer ergonomics. Spec revisions and SDK ergonomics end up coupled through one versioning stream.
  3. Dedicated @did-btcr2/api package. An SDK facade that sits above method, re-exports selected surface from every layer-one package, owns the consumer-facing configuration shape, and has its own versioning cycle independent of spec changes.

Decision

Option 3. On 2025-09-26 (commit 74b517e), @did-btcr2/api@0.1.0 is initialized with the explicit role of consumer-facing SDK facade. The package:

The boundary rule is: method is the spec; api is the SDK. A test vector generator or cross-language parity test might consume method directly. Application code: CLIs, wallets, services: consumes api. ADR 009 later expands the sub-facade tree. ADR 024 later adds lazy construction and layered config on top of this boundary.

Consequences

Positive

Negative

Explicitly accepted trade-offs

References