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:

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:

  1. Declare what your app offers and wants as ExchangeItem values (assets, capabilities, services, storage, namespace entries).
  2. Post the intent to the solver via POST /api/intent on your node.
  3. When matched, the solver proposes a ring settlement.
  4. 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.