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, Workers:Edit, Account:Workers KV:Edit
  • 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", NODE_TYPE = "standard" }
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

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

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>

Then set payable: true and an admin contact in your node config so the manifest advertises it:

{
"payable": true,
"admin": "admin@mycompany.com"
}

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)
  • 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.