Building Apps on dregg
A dregg app is an HTTP service that uses dregg-app-framework
for infrastructure and dregg-sdk for cell/turn/proof
operations. The framework gives you health checks, CORS, admin auth,
persistence, and proof verification out of the box.
Using AppServer
The AppServer builder from dregg_app_framework::server
sets up the standard middleware stack:
use dregg_app_framework::server::{AppConfig, AppServer};
use dregg_app_framework::{AdminAuth, JsonPersistence, DreggEngine};
use axum::{Router, routing::post};
#[tokio::main]
async fn main() {
let config = AppConfig::from_env();
// Initialize the dregg engine (connects to node, manages cipherclerk)
let engine = DreggEngine::new(EngineConfig::from_env()).await;
// Build the app
AppServer::new(config)
.service_name("my-app")
.with_health() // GET /health
.with_cors() // permissive CORS for SPAs
.routes(app_routes(engine))
.serve()
.await
.unwrap();
}
fn app_routes(engine: DreggEngine) -> Router {
Router::new()
.route("/api/action", post(handle_action))
.with_state(engine)
}
Environment variables: LISTEN (bind address, default
0.0.0.0:3000), DREGG_NODE_URL (federation
node), DREGG_ADMIN_TOKEN (for /admin/* routes),
DREGG_STATE_FILE (JSON persistence path).
Register in the Nameservice
Apps are discovered via the governed namespace. Register your service so clients can find it by name:
# From the CLI:
dregg namespace mount "services/my-app" \
--endpoint "https://my-app.example.com" \
--cell-id <your-service-cell-id>
The DFA routing table classifies requests by namespace path. Governed namespaces enforce naming policy via Datalog rules.
Accept CapTP Connections
Clients connect to your app by enlivening a sturdy ref. The framework's proof verification middleware validates presentation proofs from HTTP headers automatically:
use dregg_app_framework::middleware::VerifiedPresentation;
async fn handle_action(
VerifiedPresentation(proof): VerifiedPresentation,
// ... other extractors
) -> impl IntoResponse {
// `proof` is a validated AuthorizationPresentation
// The client proved they hold the required capability
// without revealing the capability itself
}
Programmable Queues for Validated Input
Rather than accepting raw HTTP bodies, apps can use programmable queues -- storage buffers with custom acceptance predicates. The queue validates messages before your app ever sees them:
- Define an acceptance predicate (a circuit that must verify against the message).
- Messages that fail the predicate are rejected at the storage layer.
- Your app polls the queue for pre-validated, STARK-proven inputs.
This moves validation out of your application code and into the cryptographic layer -- you cannot process an invalid message.
Participate in Ring Trades
The generalized intent solver matches heterogeneous offers and wants across participants. To participate:
- Declare what your app offers and wants as
ExchangeItemvalues (assets, capabilities, services, storage, namespace entries). - Post the intent to the solver via
POST /api/intenton your node. - When matched, the solver proposes a ring settlement.
- Your app commits (escrow + STARK proof of fulfillment) or rejects.
Settlement is atomic: either all participants commit or none do. The
commit-reveal protocol uses CommitRevealFulfiller from the
app framework.
Minimal App Skeleton
// Cargo.toml
// [dependencies]
// dregg-app-framework = { path = "../app-framework" }
// dregg-sdk = { path = "../sdk" }
// axum = "0.8"
// tokio = { version = "1", features = ["full"] }
// serde_json = "1"
use dregg_app_framework::server::{AppConfig, AppServer};
use dregg_app_framework::{DreggEngine, JsonPersistence, CellId};
use dregg_sdk::embed::EngineConfig;
use axum::{Router, Json, routing::{get, post}};
use serde_json::{json, Value};
struct AppState {
engine: DreggEngine,
persistence: JsonPersistence,
}
#[tokio::main]
async fn main() {
tracing_subscriber::init();
let config = AppConfig::from_env();
let engine = DreggEngine::new(EngineConfig::from_env()).await;
let persistence = JsonPersistence::new(
config.state_file.clone().unwrap_or("state.json".into())
);
let state = std::sync::Arc::new(AppState { engine, persistence });
AppServer::new(config)
.service_name("minimal-app")
.with_health()
.with_cors()
.routes(
Router::new()
.route("/api/status", get(status))
.route("/api/action", post(action))
.with_state(state)
)
.serve()
.await
.unwrap();
}
async fn status() -> Json<Value> {
Json(json!({ "status": "ok" }))
}
async fn action(
axum::extract::State(state): axum::extract::State<std::sync::Arc<AppState>>,
Json(body): Json<Value>,
) -> Json<Value> {
// Your app logic here: validate, execute turns, respond
Json(json!({ "accepted": true }))
}
See apps/ in the repository for production examples:
stablecoin, AMM, orderbook, lending, identity, gallery, compute-exchange,
bounty-board, and governed-namespace.