Skip to main content

Adding a domain to an existing product

delta's design is uniquely suited for integrating with pre-existing systems. A domain's execution logic can exist in any language, calling the delta gateway APIs only when interacting with assets stored on delta, and using any language. This guide provides a high-level walkthrough of connecting an existing third-party product to a delta domain.

Domain architecture​

A delta domain built on an existing product consists of the following components:

  • Existing product — your frontend, backend logic, existing APIs, and databases
  • delta Gateway — a Docker-deployable execution environment that exposes a REST API and handles core delta primitives so developers do not need to implement these from scratch
  • Integration logic — calls from your application backend to the delta Gateway API, built as part of domain development, that map existing product concepts to delta primitives
  • Guardrails (optional) — domain-specific rules written in Rust that must be proven before a state change can be finalized on the delta settlement layer

The existing product and the delta Gateway run side-by-side. Your backend calls the Gateway API only when a delta state change is required. For everything else, it continues using its existing logic.

delta transaction primitives​

The following delta transaction types are available out of the box via the Gateway and form the base of all delta-related state changes. For the full list of transaction types in the delta protocol, see our page on transactions. Any action not listed here (such as a swap) is a custom transaction type that your domain defines and implements using these primitives as components.

Primitive (verifiableType)PurposeSigner
debitAllowanceAuthorizes a debit from a vault up to a maximum amountSigning key of token holdings vault
fungibleTokenMintCreates a new fungible token or increases the supply of an existing oneSigning key of mint vault
nftMintCreates a new NFT collection or increases the supply of an existing oneSigning key of mint vault

For a complete reference of primitive schemas and example payloads, see the API docs.

Custom transaction types​

Most domains require logic that goes beyond these primitives. For example, a payment that simultaneously updates a record in your existing database, or a token mint that is conditional on a third-party API response. Custom transaction types let you combine delta primitives with domain-specific data in a single signed message.

Custom transaction data is passed via the auxiliary field of the Payload wrapper:

pub struct Payload<D> {
content: D, // the delta primitive data (e.g. DebitAllowance contents)
auxiliary: Vec<u8>, // arbitrary bytes, also signed
}

The auxiliary field is arbitrary bytes, and the domain defines the contents. Typically this will be a serialized struct containing the domain-specific fields relevant to the action. Because auxiliary is included in the signed payload, its contents can be verified by guardrails at settlement time.

Example: A domain that represents a lending platform might define a custom transaction type for a loan repayment. The debitAllowance primitive handles the token movement, and the auxiliary field carries the loan ID and repayment schedule terms that the borrower explicitly signs over.

// Domain-defined custom data
const repayment = {
loan_id: "loan_abc123",
installment_number: 3,
}

const auxiliary = new TextEncoder().encode(JSON.stringify(repayment))

// Sign a debitAllowance primitive with the custom data attached
const signed = await signer.signedDebitAllowance(debitJson, auxiliary)

The signed message is then submitted to the Gateway via POST /intents/execute. The Gateway applies the delta state change locally; the auxiliary data is preserved in the transaction record and available for guardrail validation at settlement time.

Note on naming: The term "custom transaction type" refers to this pattern of attaching domain-specific data to a delta primitive — it is not a separate primitive type registered with the Gateway. The Gateway handles the primitive; your domain defines and interprets the auxiliary data.

Integration flows​

Depending on your product's architecture, delta primitives can be triggered in three ways.

Backend-initiated flow​

An external event causes your backend to automatically construct and sign a delta action. This is appropriate for automated processes such as scheduled payouts, event-driven mints, or responses to webhooks from a third-party system.

In this flow, your backend holds an Ed25519 keypair that owns the relevant vault. When the triggering event occurs, the backend constructs the payload, signs it using the Rust sign() method from the domain SDK, and submits it to the Gateway /intents endpoint.

User-initiated flow​

A user action in the product frontend triggers a delta state change. The user signs the delta payload directly on their device using the TypeScript signing SDK, typically with a passkey.

The user signs over both the primitive and the auxiliary filed data, meaning the user's signature covers the full context of the action, including any domain-specific terms or parameters.

Event-driven flow​

A delta event (such as an external credit arriving from another domain) triggers logic in your application.

The Gateway's local state view reflects incoming credits from other domains. Your backend can poll or subscribe to relevant vault state changes, and trigger the responding process.

Guardrails​

Guardrails are optional domain-specific rules that must be satisfied before a state change can be finalized on the delta settlement layer. They are written in Rust and compiled to run inside a zero-knowledge virtual machine, producing a cryptographic proof that the rules were followed.

In the context of a third-party integration, guardrails are most useful for enforcing constraints that span both your existing system and delta — for example, ensuring that a delta state change only settles if a corresponding operation in your external system also succeeded. The auxiliary field in a signed message payload is useful to include domain-specific data into guardrail validation.

Because guardrail logic runs inside a zkVM and the resulting proof is verified at the settlement layer, guardrails provide cryptographic guarantees rather than application-level trust. Failed guardrail validation prevents settlement without requiring intervention from your backend.

For more information, see our guide on implementing guardrails.

Settlement​

Transactions submitted via POST /intents/execute are applied to the Gateway's local state immediately but are not yet finalized on the settlement layer. To settle:

  1. Call POST /sdls/submit to bundle pending state diffs into a State Diff List (SDL)
  2. Call POST /sdls/{hash}/prove to generate a ZK proof (this includes guardrail proof generation if guardrails are configured)
  3. Call POST /sdls/{hash}/submit_proof to submit the proof to the settlement layer

See the API docs for the full SDL endpoint reference.