CLI: @did-btcr2/cli
@did-btcr2/cli is the command-line tool of the TypeScript implementation. The
btcr2 command wraps the SDK. It also keeps your keys in an
encrypted keystore, your endpoints in a config file, and a record of each of
your identifiers.
Install
Section titled “Install”npm install -g @did-btcr2/clibtcr2 --versionThe CLI needs Node.js 24.7 or newer. To run the CLI with no install, use
npx @did-btcr2/cli <command>.
Configure
Section titled “Configure”btcr2 initinit makes the home directory ~/.btcr2, a config file, and an encrypted
keystore. It asks for a new passphrase. A second run changes nothing.
The DID gives the network to resolve, update, and deactivate. create and
genesis build take the network from -n, else from the active profile, else
from defaults.network in config.json. A setting comes from the first of: a
flag, an environment variable, the profile in config.json, defaults.cas in
config.json (CAS settings only), the default of the network. The default REST
hosts are the same as in the SDK.
# Optional: your own IPFS node as the CAS of all networks. The default CAS is# the read-only gateway https://trustless-gateway.link.btcr2 config set defaults.cas.rpcUrl 'http://127.0.0.1:5001'Other settings:
--btc-rest <url>sets your own Esplora host. The host must meet the Esplora server requirements.--btc-rpc-url <url>sets a Bitcoin Core RPC endpoint.--cas-rpc-url <url>connects a writable CAS (the RPC endpoint of an IPFS node).btcr2 config effective -n mutinynetshows each setting and its source.btcr2 config doctor -n mutinynettests the endpoints.
The keystore is one file (~/.btcr2/keystore.json). Each secret key is
encrypted with argon2id and XChaCha20-Poly1305. The CLI gets the passphrase from
BTCR2_KEYSTORE_PASSPHRASE, then --passphrase-file, then an unlocked session,
then a prompt. btcr2 keystore unlock --ttl 2h keeps a session, and
btcr2 keystore lock ends it.
The CLI keeps one record for each identifier in ~/.btcr2/dids.json: a name,
the keys, the signing key, and the sidecar data. The record holds no secret key.
create, update, and deactivate write the record. resolve, update,
deactivate, and message accept the name of a record in -i, and use its
sidecar data.
btcr2 identifier list shows the records.
Create
Section titled “Create”Creation is offline: no chain read and no fee. There are two identifier types:
k1(deterministic): the identifier encodes a compressed secp256k1 public key. The initial DID document follows from the key.x1(external): the identifier encodes the SHA-256 hash of a Genesis Document.genesis buildbuilds one from keys, beacons, and services. Keep the document: a resolver needs it as sidecar data.
btcr2 create -n mutinynet --name alice --verbosecreate uses the default key. If the keystore has no key, create makes a new
key, stores it in the keystore, and makes it the active key. The command prints
the DID. --verbose also prints the key and the beacon address to fund. The
record alice keeps the key that signs the updates.
btcr2 key generate --name alice-keycat > spec.json <<'JSON'{ "verificationMethods": [{ "key": "alice-key" }], "services": [ { "id": "#website", "type": "LinkedDomains", "serviceEndpoint": "https://example.com" } ]}JSONbtcr2 genesis build -n mutinynet --spec spec.json --out genesis.jsonbtcr2 create -t x -n mutinynet --document genesis.json --name alicebtcr2 identifier add alice -k alice-keygenesis build writes the genesis document to genesis.json. It prints the DID
and the beacon address to fund. create -t x records the DID and its genesis
document as alice, and identifier add -k sets the key that signs the
updates. Use a key that no other DID uses: two DIDs with one key share a beacon
address, and Bitcoin then links them.
Resolve
Section titled “Resolve”btcr2 resolve -i aliceThe CLI reads the beacon signals from the network of the DID and applies each
update that the sidecar data or the CAS supplies. The output is the DID document
and its metadata (versionId, confirmations, deactivated, and updated
after an update).
Sidecar data comes from the DID controller:
- An
x1DID needs its genesis document. - A DID with updates needs every signed update, unless a CAS holds them.
For your own DID, the record supplies the sidecar data. For another DID, get
the sidecar data file from its controller and add it to a record:
btcr2 identifier add <did> --sidecar sidecar.json. The flags
--genesis-document, -r (inline JSON), and -p (a JSON file) also supply
sidecar data. versionId and versionTime in the resolution options select an
earlier version of the document.
Update
Section titled “Update”An update is a JSON Patch to the
DID document. The CLI signs the update and broadcasts a beacon signal. The
signal is a Bitcoin transaction with the hash of the update in its OP_RETURN
output, so the beacon address must hold confirmed bitcoin. The value must be
more than the fee. Fund the beacon address that create --verbose printed, and
wait for one confirmation.
DID=$(btcr2 -o json identifier show alice | jq -r '.data.identifier')PATCH=$(jq -nc --arg id "$DID#blog" \ '[{op: "add", path: "/service/-", value: {id: $id, type: "LinkedDomains", serviceEndpoint: "https://blog.example.com"}}]')btcr2 update -i alice -p "$PATCH"The key of the record signs the update. --signing-key <ref> selects another
key. The CLI adds the signed update to the record. Bitcoin holds only its hash:
a resolver needs the signed update as sidecar data, unless you publish it to a
CAS. btcr2 identifier sidecar alice --out sidecar.json writes the sidecar data
file for the relying parties.
Before the next update or deactivate, wait until the beacon signal of the
last update has 6 confirmations. If the resolution does not apply all updates of
the record, the CLI refuses the command.
-m (the verification method) and -b (the beacon) are optional: the CLI uses
the method that publishes the signing key and the beacon that can fund the
signal. --fee-rate sets the fee rate in sat/vB. The default is 5 sat/vB.
Publish to a CAS
Section titled “Publish to a CAS”By default, the CLI publishes nothing (--publish-to-cas never). The controller
then gives each signed update to the relying parties as sidecar data, and only
they can see the change. With a writable CAS, --publish-to-cas auto publishes
the signed update before the broadcast. Any resolver can then get the update
from the CAS with no sidecar data. always refuses the update if no writable CAS
is configured.
btcr2 update -i alice -p "$PATCH" --publish-to-cas auto --cas-rpc-url http://127.0.0.1:5001Deactivate
Section titled “Deactivate”Deactivation is an update with the patch
[{ "op": "add", "path": "/deactivated", "value": true }].
btcr2 deactivate adds the patch for you. Deactivation is permanent.
btcr2 deactivate -i aliceThe record supplies the signing key and the sidecar data of the earlier updates. The CLI adds the deactivation to the record. A resolver needs it as sidecar data to see the deactivation, so give the relying parties a new sidecar data file.
Sign a message
Section titled “Sign a message”btcr2 message sign -i alice "I control this DID. 2026-10-08 nonce 7f3a" > message.jsonmessage sign resolves the current DID document and signs the text with the key
of the record. The output is the text and a bip340-jcs-2025 Data Integrity
proof with the proof purpose assertionMethod. Thus a message signature is never
valid as an update proof or as a transaction signature. The format has no time
and no replay protection: put the date, a nonce, and the audience in the text.
The shell history shows the text, so do not sign a secret.
The specification does not define message signatures: the format is a choice of this implementation.
btcr2 message verify -i did:btcr2:k1q... --sidecar sidecar.json message.jsonmessage verify checks the signed message against the current DID document. It
needs the sidecar data of the DID, as resolve does. A failed check gives the
exit code 1. After a key rotation or a deactivation, an old message fails. The
message reference
also shows how to verify a message with no btcr2 code.
Use in scripts
Section titled “Use in scripts”-o jsonprints{ "action": ..., "data": ... }. Hints go to stderr.-q(--quiet) removes them.- The exit code is
0on success and1on an error. - A script with no terminal needs
BTCR2_KEYSTORE_PASSPHRASEor--passphrase-fileto sign.
Reference
Section titled “Reference”btcr2 <command> --helplists the flags of a command.- CLI reference: one page per command, and a longer walkthrough in DEMO.md.
@did-btcr2/clion npm and its changelog.