Design and implement a self-contained constraint DSL that serves as a machine-consumable “interface layer” for Cardano smart contracts. The goal is to enable wallets, explorers, and other off-chain programs to construct transactions that are guaranteed to satisfy a script’s validation rules, without requiring bespoke per-dApp tx-building logic.
In Phase 1, the DSL will allow a contract author (or a third-party verifier) to publish a compact, deterministic specification of:
Datum and redeemer schemas (structured views over Data)
Intent/redeemer endpoints (e.g., Withdraw, Cancel, Update, etc.)
For each redeemer: the full set of constraints required for a successful spend/mint, including constraints over:
input/output UTxOs and their datum relationships (state transitions)
value preservation and token flow constraints
required signers / authorization predicates
validity interval / time bounds
reference inputs, script purpose, and required script context properties
The DSL will compile to an efficient binary format suitable for on-chain/off-chain distribution. Off-chain, we provide a reference toolchain that compiles the binary specification into a Z3 constraint system, and uses Z3 to produce witness assignments (concrete datums, redeemers, and transaction-shape parameters) that satisfy the constraints. Consumer applications can then use these witnesses to programmatically construct complete transactions that pass the target script’s validation logic.
Phase 1 is explicitly off-chain enforced: constraints are not checked by the ledger, but are intended to be sufficient for transaction construction and interoperability. The design will preserve forward compatibility for potential future extensions (e.g., on-chain commitment to interface hashes or native ledger support for typed/constraint-aware interfaces), while delivering immediate value by enabling generalized tx-building from published constraint modules.
This is a minimal, end-to-end example of a Phase 1 constraint module that describes everything an off-chain program needs to construct a transaction that will satisfy a script’s validation logic for a given redeemer.
module TimelockV1
// ----- Datum / Redeemer schemas over Data -----
datum TimelockDatum =
Constr 0 {
beneficiary : Address,
unlock_after : Time // POSIX millis
}
purpose SpendingScript => redeemer Unlock = Constr 0 {}
// ----- Success conditions for redeemer Spend -----
endpoint Unlock {
requires signer == datum.beneficiary
requires tx.valid_from >= datum.unlock_after
// Minimal value constraint: all value leaves the script (no continuing outputs)
requires script.continuing_outputs.count == 0
}The compiler turns the endpoint into solver variables + constraints:
; Variables the builder must choose / the tx must satisfy
(declare-const signer Address)
(declare-const valid_from Int)
(declare-const continuing_outputs_count Int)
; Datum fields (these come from decoding the actual datum)
(declare-const beneficiary Address)
(declare-const unlock_after Int)
; Constraints
(assert (= signer beneficiary))
(assert (>= valid_from unlock_after))
(assert (= continuing_outputs_count 0))
(check-sat)
(get-model){
"endpoint": "Unlock",
"purpose": "SpendingScript",
"tx": {
"required_signers": ["addr1...beneficiary..."],
"validity_start": 1746572854000,
"validity_end": 1746573154000
},
"redeemer": { "constructor": 0, "fields": [] }
}