Skip to Content
Scalus Club is now open! Join us to get an early access to new features 🎉
DocumentationOffchain ApplicationsWorking with Contract

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 Action

Step 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 HtlcValidator

Step 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 name
  • description: Detailed explanation of functionality
  • version: Semantic version string
  • compiler: Compiler information (use CompilerInfo.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.releaseCompiledContract

Using 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
Last updated on