Skip to main content

Build with the Generic Domain

The generic domain is a reference implementation of a domain built with the delta Domain SDK. It exposes a REST API with OpenAPI documentation, supports token transfers, token minting, and NFT collections out of the box.

For most use cases, you can run the generic domain as a Docker image and build your application on top.

API Overview​

The domain API is specified by OpenAPI. Check out the API docs. The API is organized into three endpoint groups:

Vaults​

Query the current state of vaults on the domain:

  • GET /vaults/{owner_id} — Get a vault's balance and token holdings
  • GET /vaults/{owner_id}/nonce — Get the next expected nonce for replay protection

These endpoints read from the domain state view, which reflects all locally applied transactions (even those not yet settled to the base layer) and also the incoming credits from external domains.

Intents​

Submit signed user-level transactions:

  • POST /intents/execute — Submit a signed transaction for default execution
  • POST /intents/submit — Submit a transaction with pre-computed state diffs

The execute endpoint is the standard path: you submit a signed debit allowance or token mint, and the domain computes and applies the resulting state changes. The submit endpoint is for advanced use cases where you want to compute the state diffs yourself.

SDLs​

Manage State Diff Lists for settlement:

  • GET /sdls — List all SDLs and their states
  • GET /sdls/{hash} — Get a specific SDL's status
  • POST /sdls/submit — Create an SDL from pending state diffs
  • POST /sdls/{hash}/prove — Generate a zk proof for an SDL
  • POST /sdls/{hash}/submit_proof — Submit the proof to the base layer

This is the settlement flow: transactions applied via /intents/execute are initially local. To finalize them on delta, you submit an SDL, generate a proof, and submit that proof to the base layer.

The running domain also exposes the full OpenAPI specification as well as the API docs under /openapi.json and /swagger-ui respectively.

Run the Domain​

To run the generic domain with Docker, first create a configuration file in your working directory:

domain.yaml
shard: <DOMAIN_SHARD_ID>
keypair: /app/keypair.json
rpc_url: <RPC_URL>

The RPC_URL points to the [RPC endpoint][validator-rpc] of the base layer network (e.g. the delta testnet), and the private key in your keypair.json must be pre-seeded with base layer funds. This and the DOMAIN_SHARD_ID are provided by the delta team.

Pull and run the container from the registry. Mount your keypair and configuration files accordingly:

docker run \
--name delta-domain \
-p 3000:3000 \
-v $(pwd)/domain.yaml:/app/domain.yaml \
-v $(pwd)/keypair.json:/app/keypair.json \
ghcr.io/repyh-labs/delta-generic-domain:latest

It is advisable to set a fixed version like 0.6.8 for the Docker image instead of latest to retain control over upgrades. Note that the keypair you're using needs to have some pre-seeded funds in order to work with it.

The domain automatically handles the domain agreement — it checks if an agreement exists for your shard, and if not, submits one and waits for it to become active.

Verify the domain is running by visiting http://localhost:3000/swagger-ui.

Generate a Client​

To generate code for API clients, you need the OpenAPI specification available at /openapi.json when the domain is running.

Example: TypeScript​

We show how to create client in TypeScript. This will also generate type definitions of all required types.

npx openapi-typescript-codegen \
--input http://localhost:3000/openapi.json \
--output src/generated \
--client fetch

This generates typed service classes (VaultsService, IntentsService, SdlsService) that you can import directly.

Other Languages​

The OpenAPI Generator supports 50+ languages and frameworks, including Python, Go, Java, Kotlin, Swift, and more.

Sign Transactions​

While the generated API client handles HTTP communication, you need to sign transactions before submitting them.

For TypeScript/JavaScript, use the @repyh-labs/delta-signing package:

import { Ed25519Signer } from "@repyh-labs/delta-signing"

const signer = new Ed25519Signer(...)

const signed = await signer.signedDebitAllowance(debitJson)

Custom Transaction Types​

To create custom transaction types, you can use the auxiliary field of the transaction payload. As an example, let's imagine the user signs over a maximal slippage amount for a certain swap operation.

The auxiliary field expects raw bytes. We therefore serialize the additional data. For this example, we just encode the custom data as a UTF-8 JSON string:

const data = { max_slippage: 1.1 }

const auxiliary = new TextEncoder().encode(JSON.stringify(data))
const signed = await signer.signedDebitAllowance(debitJson, auxiliary)

This additional data can later be used to prove certain properties on the transaction in local law. This will prevent the settlement of any transaction that does not satisfy the local laws.

To read the auxiliary data inside the local laws implementation, we have to parse the JSON into the expected type:

#[derive(serde::Deserialize)]
struct SwapData { max_slippage: f64 }

impl LocalLaws for SwapLaws {
...
fn validate(transactions: ...) -> ... {
for tx in transactions {
if let VerifiableType::DebitAllowance(debit) = &tx.verifiable {
let bytes = debit_allowance.payload().auxiliary();
let data: SwapData = serde_json::from_slice(bytes)?;

// ... use data.max_slippage to prove properties
}
}
}
}

Local vs Settled State​

An important concept when building on the domain: transactions applied via /intents/execute take effect immediately in the domain's local state, but they're not yet finalized on the base layer.

This is by design — it allows the domain to:

  • Provide instant feedback to users
  • Aggregate multiple transactions into a single SDL
  • Apply custom validation before settlement

To finalize transactions, you call the SDL endpoints to submit, prove, and settle. Until then, the base layer view of vaults may differ from the domain's local view.

Next Steps​

You now have a foundation for building applications on the generic domain. From here you can:

  • Build a web frontend using the same generated client
  • Add custom business logic on top of the domain's primitives
  • Integrate with your existing backend services