Relay Operators
A relay operator runs a hosted inbox service. Users who are frequently offline (mobile cipherclerks, IoT agents, intermittent peers) subscribe to a relay operator. The relay buffers store-and-forward messages in MerkleQueue-backed inboxes, provides cryptographic delivery proofs, and earns fees for the service.
What is a Relay Operator?
- Hosts inboxes for subscribed users (bounded capacity, metered storage)
- Accepts incoming CapTP store-and-forward messages on behalf of offline users
- Delivers messages with cryptographic DequeueProofs when users come online
- Charges sender deposits (anti-spam) and subscription fees (capacity reservation)
- Earns 10% of expired message deposits as revenue on GC
- Subject to SLA: must deliver within
max_delivery_latencyblocks or be slashed
Requirements
- Bond: Computrons staked proportional to hosted capacity (100 computrons per unit of capacity)
- Uptime: Reliable availability -- missed deliveries within the SLA window trigger slashing
- Storage: Sufficient disk for hosted inboxes (each message is typically 1-64 KiB)
- Network: Public IP or domain with ports accessible to senders and subscribers
Quick Start
# Initialize the node data directory (generates operator keypair)
dregg-node init --data-dir ~/.dregg
# Start the relay operator service
dregg-node relay \
--bond 10000 \
--port 3100 \
--state-file ./relay-state.json \
--data-dir ~/.dregg
The relay service starts an HTTP API on the specified port. It reads the
operator identity from ~/.dregg/node.key.
Configuration Options
| Flag | Default | Description |
|---|---|---|
--port | 3100 | HTTP API listen port |
--bond | 10000 | Bond amount in computrons (operator stake) |
--max-capacity | 100000 | Maximum total inbox capacity to host |
--gc-interval | 300 | GC interval in seconds |
--message-ttl | 1000 | Message TTL in blocks (expired messages are GC'd) |
--max-delivery-latency | 50 | SLA: max blocks before delivery is required |
--state-file | ./relay-state.json | Path for persistent relay state |
--data-dir | ~/.dregg | Data directory (operator key location) |
--default-inbox-capacity | 100 | Default capacity for new inbox subscriptions |
--default-min-deposit | 100 | Default minimum deposit per message |
--min-message-deposit | 100 | Global minimum deposit per message (computrons) |
--subscription-fee | 1000 | One-time fee for creating an inbox |
HTTP API Endpoints
| Method | Path | Description |
|---|---|---|
| GET | /relay/status | Operator info: bond, hosted inboxes, earnings, health |
| POST | /relay/subscribe | Create a hosted inbox (user pays subscription fee) |
| DELETE | /relay/unsubscribe | Remove an inbox (remaining messages refunded) |
| POST | /relay/send/:dest | Enqueue a message (sender pays deposit) |
| GET | /relay/drain | Drain your inbox (authenticated, returns messages + proofs) |
| GET | /relay/inbox/:id/status | Check inbox status (capacity, pending, root) |
| GET | /relay/proof/:msg_id | Get the DequeueProof for a delivered message |
Economics
Revenue Sources
- Subscription fees: One-time fee when a user creates an inbox
- GC fees: 10% of expired message deposits are kept by the operator
- Delivery compensation: On successful delivery, deposits flow to the inbox owner (who may share with the operator via separate agreement)
Fee Flow
Sender deposits 1000 computrons with a message
|
+-- Message delivered within TTL:
| Deposit goes to inbox owner (compensation for reading)
|
+-- Message expires (GC):
90% refunded to sender
10% kept by relay operator as fee
Bond Economics
The bond requirement scales linearly with committed capacity:
required_bond = total_committed_capacity * 100 computrons.
If an operator becomes underbonded (e.g., bond is slashed), they cannot accept
new inboxes until the bond is topped up.
Disputes and Slashing
If a relay operator fails to deliver a message within the SLA window
(max_delivery_latency blocks), a sender can file a
DeliveryDispute:
- Sender proves they enqueued (provides old queue root + enqueue receipt)
- Operator must respond with a valid DequeueProof within the SLA window
- If operator cannot produce proof: slashed (bond / active_inboxes)
- If operator produces valid proof: dispute dismissed
Slashing is proportional: slash_amount = bond / active_inbox_count.
An operator hosting 10 inboxes with a 100,000 computron bond risks losing
10,000 computrons per failed delivery dispute.
Monitoring
Use GET /relay/status to monitor operator health:
curl http://localhost:3100/relay/status | jq .
{
"operator_id": "aa0011...",
"bond": 100000,
"required_bond": 50000,
"is_underbonded": false,
"active_inboxes": 5,
"total_pending_messages": 142,
"earned_fees": 4200,
"max_delivery_latency_blocks": 50,
"current_height": 12450,
"messages_delivered": 8923,
"messages_received": 9065,
"gc_interval_secs": 300
}
What to Watch
- is_underbonded: If true, you cannot accept new inboxes. Top up your bond.
- total_pending_messages: High and growing = users not draining. Consider reducing capacity or contacting subscribers.
- earned_fees: Your cumulative revenue from GC passes.
- messages_delivered vs messages_received: Delivery ratio. Low ratio may indicate inactive subscribers.
Scaling
- Capacity planning: Each inbox unit reserves space for one message. A capacity of 100 means 100 messages can be buffered simultaneously.
- Bond scaling: 100 computrons per capacity unit. Hosting 1000 total capacity requires 100,000 computron bond.
- Multiple instances: Run multiple relay processes on different ports with different operator keys for horizontal scaling.
- Storage growth: Messages are GC'd after TTL expires. Disk usage is bounded by
max_total_capacity * max_message_size.
Discovery (Governed Namespace)
Relay operators register in the governed namespace so that clients can discover available relays. The namespace entry includes the operator's public key, endpoint URL, fee policy, and current capacity availability. Clients query the namespace to find a relay that meets their needs (price, capacity, SLA).