Working with Contract
In this guide we cover how to compile your Scalus validators and integrate them with off-chain applications.
What is a Contract?
A Contract is a wrapper around your validator that provides:
- Multiple compilation variants (debug, default, release)
- Type-safe datum and redeemer schemas
- Automatic blueprint generation (CIP-57)
- Metadata and documentation
- Easy access to script and address
Creating a Contract
Let’s walk through creating a contract for a Hash Time-Locked Contract (HTLC).
Step 1: Define Your Types
import scalus.*
import scalus.builtin.{ByteString, Data}
import scalus.builtin.Data.{FromData, ToData}
import scalus.ledger.api.v3.*
import scalus.prelude.*
// Define the contract state (datum)
case class ContractDatum(
committer: ByteString,
receiver: ByteString,
image: ByteString,
timeout: PosixTime
) derives FromData, ToData
@Compile
object ContractDatum
// Define the actions (redeemer)
enum Action derives FromData, ToData:
case Timeout
case Reveal(preimage: ByteString)
@Compile
object ActionStep 2: Implement the Validator
import scalus.builtin.Builtins.sha3_256
@Compile
object HtlcValidator extends Validator:
inline override def spend(
datum: Option[Data],
redeemer: Data,
tx: TxInfo,
ownRef: TxOutRef
): Unit = {
val ContractDatum(committer, receiver, image, timeout) =
datum.map(_.to[ContractDatum]).getOrFail("Missing datum")
redeemer.to[Action] match
case Action.Timeout =>
require(tx.isSignedBy(committer), "Must be signed by committer")
require(
tx.validRange.isEntirelyAfter(timeout),
"Can only timeout after deadline"
)
case Action.Reveal(preimage) =>
require(tx.isSignedBy(receiver), "Must be signed by receiver")
require(
!tx.validRange.isEntirelyAfter(timeout),
"Must reveal before deadline"
)
require(sha3_256(preimage) === image, "Invalid preimage")
}
end HtlcValidatorStep 3: Wrap in a Contract
import scalus.cardano.blueprint.{CompilerInfo, Contract, Preamble}
import scalus.cardano.ledger.Language
lazy val HtlcContract = Contract.PlutusV3Contract[ContractDatum, Action](
Preamble(
title = "Hashed Time-Locked Contract",
description = Some(
"Releases funds when recipient reveals hash preimage before deadline, " +
"otherwise refunds to sender."
),
version = Some("1.0.0"),
compiler = Some(CompilerInfo.currentScalus),
plutusVersion = Some(Language.PlutusV3),
license = None
),
HtlcValidator.validate // Reference to the validator's validate method
)Preamble fields:
title: Short descriptive namedescription: Detailed explanation of functionalityversion: Semantic version stringcompiler: Compiler information (useCompilerInfo.currentScalus)plutusVersion: Target Plutus version (PlutusV1, PlutusV2, or PlutusV3)license: Optional license identifier
Three Compilation Variants
Each contract provides three pre-compiled variants:
// Debug: Includes error traces and debugging information
val debugContract = HtlcContract.debugCompiledContract
// Default: Balanced compilation with standard optimizations
val defaultContract = HtlcContract.defaultCompiledContract
// Release: Optimized for production, minimal script size
val releaseContract = HtlcContract.releaseCompiledContractUsing Contracts in Transactions
Access the compiled script and use it in your transaction builder:
import scalus.cardano.address.Address
import scalus.cardano.ledger.*
import scalus.cardano.txbuilder.*
// Get the compiled contract (choose variant based on environment)
val compiledContract = HtlcContract.defaultCompiledContract
// Access the script and script address
val script = compiledContract.script
val scriptAddress = Address(network, Credential.ScriptHash(script.scriptHash))Locking funds at the script:
def lock(
env: Environment,
inputUtxo: Utxo,
changeAddress: Address,
value: Value,
datum: ContractDatum
): Transaction = {
TxBuilder(env)
.spend(inputUtxo)
.payTo(scriptAddress, value, datum.toData)
.changeTo(changeAddress)
.build()
}Unlocking funds with a redeemer:
def unlock(
env: Environment,
lockedUtxo: Utxo,
collateralUtxo: Utxo,
redeemer: Action.Reveal,
recipientAddress: Address
): Transaction = {
TxBuilder(env)
.attach(script)
.spend(lockedUtxo, redeemer.toData)
.payTo(recipientAddress, lockedUtxo.output.value)
.collaterals(collateralUtxo)
.changeTo(recipientAddress)
.build()
}Accessing Contract Components
A CompiledContract provides access to all compilation artifacts:
val compiled = HtlcContract.defaultCompiledContract
// The compiled Plutus script
val script: Script.PlutusV3 = compiled.script
// The UPLC program
val program: Program = compiled.program
val scriptHex: String = program.doubleCborHex
// The CIP-57 blueprint (JSON)
val blueprint: Blueprint = compiled.blueprint
val blueprintJson: String = blueprint.show
// Script address for a network
val mainnetAddress: Address = compiled.address(Network.Mainnet)
val preprodAddress: Address = compiled.address(Network.Preprod)Blueprints (CIP-57)
What are Blueprints?
Contracts provide a way to automatically generate the CIP-57 Blueprints.
What’s included:
- Contract metadata (title, description, version)
- Datum and redeemer type schemas
- Compiled script code and hash
- Parameter definitions
Blueprint Structure
A blueprint JSON contains:
{
"preamble": {
"title": "Hashed Time-Locked Contract",
"description": "Releases funds when...",
"version": "1.0.0",
"plutusVersion": "v3"
},
"validators": [
{
"title": "HtlcValidator",
"datum": {
"title": "ContractDatum",
"schema": {
"dataType": "constructor",
"fields": [
{"title": "committer", "dataType": "bytes"},
{"title": "receiver", "dataType": "bytes"},
{"title": "image", "dataType": "bytes"},
{"title": "timeout", "dataType": "integer"}
]
}
},
"redeemer": {
"title": "Action",
"schema": {
"anyOf": [
{"title": "Timeout", "index": 0, "fields": []},
{"title": "Reveal", "index": 1, "fields": [
{"title": "preimage", "dataType": "bytes"}
]}
]
}
},
"compiledCode": "590b2a590b27010000...",
"hash": "a3b5c8d9..."
}
]
}Schemas are automatically derived from your Scala types using compile-time reflection.
Exporting Blueprints
Save the blueprint to a JSON file:
import java.io.File
val blueprintFile = File("htlc-contract-blueprint.json")
HtlcContract.defaultCompiledContract.blueprint.writeToFile(blueprintFile)Use cases:
- Documentation for contract users
- Input for code generation tools
- Contract verification and auditing
- Automated testing tools