Skip to content

Operator Quickstart

An operator runs a DRS node for a domain. If you control mycompany.com in DNS, you can run a DRS for all *@mycompany.com addresses.

Prerequisites

  • A domain with DNS managed by Cloudflare.
  • A Cloudflare API token with Zone:DNS:Edit, Account:Workers Scripts:Edit, Account:Workers KV Storage:Edit. See CI secrets for the minimal token template.
  • Node.js 20+ and pnpm.

1. Initialize

Terminal window
drs init --domain mycompany.com --cf-token <token>

This:

  • Generates an Ed25519 keypair
  • Creates KV namespaces for addresses and config
  • Creates DNS records: _drs._tcp.mycompany.com SRV and _drskey.mycompany.com TXT
  • Enables DNSSEC on the zone
  • Writes initial config to KV

Save the private key output — you’ll need it in the next step.

2. Store the signing key

Terminal window
cd packages/drs-worker
wrangler secret put DRS_SIGNING_KEY --env mycompany
# Paste the base64 private key when prompted

3. Configure wrangler.toml

Add an environment for your domain in packages/drs-worker/wrangler.toml:

[env.mycompany]
vars = { DOMAIN = "mycompany.com" }
routes = [{ pattern = "mycompany.com/*", zone_name = "mycompany.com" }]
[[env.mycompany.kv_namespaces]]
binding = "ADDRESSES"
id = "<addresses-kv-id-from-init>"
[[env.mycompany.kv_namespaces]]
binding = "CONFIG"
id = "<config-kv-id-from-init>"

4. Configure accepted pairs (optional manifest)

Terminal window
drs update-config --accepts '[
{"value_type":"USDC","transfer_type":"ethereum"},
{"value_type":"ETH","transfer_type":"ethereum"}
]' --cf-token <token> --account-id <id> --kv-namespace <config-id>

The manifest is optional. Clients MUST NOT require it — POST /resolve is the only endpoint senders actually need — but publishing it helps senders decide whether to attempt a resolution.

5. Add addresses

Terminal window
drs add-address alice --routes '[
{"value_type":"USDC","transfer_type":"ethereum","destination":"0xABC..."},
{"value_type":"ETH","transfer_type":"ethereum","destination":"0xABC..."}
]' --cf-token <token> --account-id <id> --kv-namespace <addresses-id>

6. Deploy

Terminal window
cd packages/drs-worker
wrangler deploy --env mycompany

7. Verify

Terminal window
# DNS is configured correctly
drs check-dns mycompany.com
# Manifest is live
curl https://mycompany.com/.well-known/directory.json | jq
# Resolution works
curl -s -X POST https://mycompany.com/resolve \
-H 'Content-Type: application/json' \
-d '{"address":"alice@mycompany.com"}' | jq
# Full end-to-end test (resolve + signature verification)
drs test-resolve alice@mycompany.com

Making your domain payable

The domain itself can be a payable address. Resolving mycompany.com pays the company. Resolving alice@mycompany.com pays Alice. Both work on the same node.

The domain’s default route is stored under the reserved _root key:

Terminal window
drs add-address _root --routes '[
{"value_type":"USDC","transfer_type":"ethereum","destination":"0xCOMPANY_TREASURY..."},
{"value_type":"USD","transfer_type":"ach","destination":"021000021:999999999"}
]' --cf-token <token> --account-id <id> --kv-namespace <addresses-id>

Optionally advertise an admin contact in the manifest:

Terminal window
drs update-config --admin admin@mycompany.com \
--cf-token <token> --account-id <id> --kv-namespace <config-id>

Now mycompany.com is a valid resolve target:

Terminal window
curl -s -X POST https://mycompany.com/resolve \
-H 'Content-Type: application/json' \
-d '{"address":"mycompany.com"}' | jq

Managing addresses with YAML files

For more than a handful of addresses, use YAML files instead of CLI flags. Create a directory of address files and sync them in one command:

Terminal window
drs sync --dir ./addresses --cf-token <token> --account-id <id> --kv-namespace <addresses-id>

Each file is named after the local part of the address. The filename becomes the address — alice.yaml becomes alice@yourdomain.com.

Basic address

addresses/alice.yaml
status: active
routes:
- value_type: USDC
transfer_type: ethereum
destination: "0xd8dA6BF26964aF9D7eEd9e03E53415D37aA96045"
- value_type: ETH
transfer_type: ethereum
destination: "0xd8dA6BF26964aF9D7eEd9e03E53415D37aA96045"

Multi-chain address

A user who accepts USDC on three chains:

addresses/treasury.yaml
status: active
routes:
- value_type: USDC
transfer_type: ethereum
destination: "0xAb5801a7D398351b8bE11C439e05C5B3259aeC9B"
- value_type: USDC
transfer_type: base
destination: "0x742d35Cc6634C0532925a3b844Bc9e7595f2bD18"
- value_type: USDC
transfer_type: solana
destination: "7EcDhSYGxXyscszYEp35KHN8vvw3svAuLKTzXwCFLtV"

Fiat-only address

A traditional business that only accepts bank transfers:

addresses/accounting.yaml
status: active
routes:
- value_type: USD
transfer_type: ach
destination: "021000021:123456789"
- value_type: USD
transfer_type: wire
destination: "CHASUS33:123456789"

Mixed crypto + fiat

addresses/pay.yaml
status: active
routes:
- value_type: USDC
transfer_type: ethereum
destination: "0xd8dA6BF26964aF9D7eEd9e03E53415D37aA96045"
- value_type: USD
transfer_type: ach
destination: "021000021:987654321"
- value_type: BTC
transfer_type: bitcoin
destination: "bc1qar0srrr7xfkvy5l643lydnw9re59gtzzwf5mdq"

Retired address

An address that was once active but is no longer accepting transfers. Resolves to a 404:

addresses/old-treasury.yaml
status: retired
routes: []

Directory structure

A typical node directory looks like:

addresses/
├── alice.yaml
├── treasury.yaml
├── pay.yaml
├── accounting.yaml
└── old-treasury.yaml

Run drs sync whenever you add, update, or retire addresses. The command uploads every file in the directory to KV — it’s idempotent.

What you control

As an operator, you decide:

  • Which pairs to accept — USDC on Ethereum, USD via ACH, BTC — whatever your infrastructure supports.
  • What destinations to return — permanent wallets, rotating proxies, temporary deposit addresses, payment endpoints.
  • Contract TTL — how long routing contracts are valid (30–3600 seconds).
  • Operational policy — rate limits, logging, abuse mitigation. The protocol deliberately doesn’t specify these; enforce them at your edge.
  • Internal delivery — what happens after the sender follows the routing contract is entirely your business.

The protocol gives you a standard interface. What you build behind it is up to you.

Key rotation

_drskey may contain multiple TXT records. Clients try every key and accept a signature that matches any one of them. To rotate without dropping in-flight senders:

  1. Publish the new public key as an additional _drskey record.
  2. Wait for DNS TTLs to expire.
  3. Flip the signing key in Worker Secrets.
  4. Remove the old _drskey record.

drs rotate-key automates steps 1 and 3. The old record must be removed manually once you’re confident traffic has drained.