Skip to content

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.

Terminal window
npm install -g @did-btcr2/cli
btcr2 --version

The CLI needs Node.js 24.7 or newer. To run the CLI with no install, use npx @did-btcr2/cli <command>.

Terminal window
btcr2 init

init 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.

Terminal window
# 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 mutinynet shows each setting and its source. btcr2 config doctor -n mutinynet tests 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.

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 build builds one from keys, beacons, and services. Keep the document: a resolver needs it as sidecar data.
Terminal window
btcr2 create -n mutinynet --name alice --verbose

create 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.

Terminal window
btcr2 resolve -i alice

The 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 x1 DID 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.

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.

Terminal window
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.

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.

Terminal window
btcr2 update -i alice -p "$PATCH" --publish-to-cas auto --cas-rpc-url http://127.0.0.1:5001

Deactivation is an update with the patch [{ "op": "add", "path": "/deactivated", "value": true }]. btcr2 deactivate adds the patch for you. Deactivation is permanent.

Terminal window
btcr2 deactivate -i alice

The 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.

Terminal window
btcr2 message sign -i alice "I control this DID. 2026-10-08 nonce 7f3a" > message.json

message 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.

Terminal window
btcr2 message verify -i did:btcr2:k1q... --sidecar sidecar.json message.json

message 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.

  • -o json prints { "action": ..., "data": ... }. Hints go to stderr. -q (--quiet) removes them.
  • The exit code is 0 on success and 1 on an error.
  • A script with no terminal needs BTCR2_KEYSTORE_PASSPHRASE or --passphrase-file to sign.