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

Spending UTxOs: Inputs, Scripts, and Collateral

This guide covers how to spend different types of UTxOs: from regular addresses, from script addresses, and using reference inputs.

Spending from Wallet / Public Key Address

The simplest case is spending a UTxO locked by a public key:

val utxo: Utxo = // ... UTxO from a regular address TxBuilder(env) .spend(utxo) // Add as input .payTo(recipient, Value.ada(10)) .build(changeTo = changeAddress) .sign(signer) // Must be signed by the address owner

You can also spend multiple UTxOs at once:

val utxos: Utxos = Map( input1 -> output1, input2 -> output2 ) TxBuilder(env) .spend(utxos) // Spend all UTxOs .payTo(recipient, Value.ada(20)) .build(changeTo = changeAddress)

Spending from Script Address

When spending UTxOs locked by a validator script, you must provide a redeemer and the script itself:

import scalus.builtin.Data val scriptUtxo: Utxo = // ... UTxO at script address val redeemer = MyRedeemer(...) // Your redeemer type // Spend with inline script TxBuilder(env) .spend(scriptUtxo, redeemer, script) .collaterals(collateralUtxo) // Required for script execution .payTo(recipient, scriptUtxo.output.value) .build(changeTo = changeAddress)

Script transactions require collateral inputs. These are only consumed if the script fails validation.

With Required Signers

If your validator requires additional signatures:

val requiredSigners = Set(pubKeyHash1, pubKeyHash2) TxBuilder(env) .spend(scriptUtxo, redeemer, script, requiredSigners) .collaterals(collateralUtxo) .payTo(recipient, scriptUtxo.output.value) .build(changeTo = changeAddress) .sign(signer1) .sign(signer2) // Must provide all required signatures

The requiredSigners set is added to the transaction’s required signers field, and the validator can check for these signatures in the transaction context.

Spending with Attached Scripts

You can attach a script multiple times, it will be reused for all inputs that reference it:

TxBuilder(env) .spend(scriptUtxo1, redeemer1, script) // attach a script .spend(scriptUtxo2, redeemer2, script) // Can reuse for multiple inputs .collaterals(collateralUtxo) .payTo(recipient, totalValue) .build(changeTo = changeAddress)

TxBuilder will automatically find the script in the attached scripts map and use it for validation.

Delayed Redeemers

For advanced cases where the redeemer depends on the final transaction structure, you can provide a function that computes the redeemer from the assembled transaction:

def buildRedeemer(tx: Transaction): Data = { // Compute redeemer based on final transaction val outputCount = tx.body.value.outputs.size MyRedeemer(outputCount).toData } TxBuilder(env) .spend(scriptUtxo, buildRedeemer, script) // Redeemer computed after assembly .collaterals(collateralUtxo) .payTo(recipient, scriptUtxo.output.value) .build(changeTo = changeAddress)

The redeemer function is called after the transaction is assembled but before script evaluation, allowing self-referential validation logic.

Use Cases for Delayed Redeemers

Delayed redeemers are useful when:

  1. Input index validation: Your validator needs to verify its position in the transaction inputs
  2. Output verification: The redeemer contains indices of outputs to validate
  3. Self-referencing logic: The script needs to know properties of the final transaction
// Example: Validator that checks its input index val buildIndexRedeemer: Transaction => Data = { tx => val myInputIndex = tx.body.value.inputs.indexWhere(_.transactionId == scriptUtxo.input.transactionId) Data.I(myInputIndex) } TxBuilder(env) .spend(scriptUtxo, buildIndexRedeemer, script) .collaterals(collateralUtxo) .payTo(recipient, scriptUtxo.output.value) .build(changeTo = changeAddress)

When using complete(), delayed redeemers are automatically recomputed after inputs are selected and the transaction is balanced.

Reference Inputs

Reference inputs allow scripts to read UTxOs without consuming them:

val referenceUtxo: Utxo = // ... UTxO with reference data TxBuilder(env) .spend(myUtxo) .references(referenceUtxo) // Add as reference input .payTo(recipient, Value.ada(10)) .build(changeTo = changeAddress)

Multiple reference inputs can be added:

TxBuilder(env) .spend(myUtxo) .references(refUtxo1, refUtxo2, refUtxo3) .payTo(recipient, Value.ada(10)) .build(changeTo = changeAddress)

Reference inputs are visible in the script context but are not spent.

Collateral Inputs

Script transactions require collateral to cover fees if validation fails:

val collateral: Utxo = // ... Pure ADA UTxO TxBuilder(env) .spend(scriptUtxo, redeemer, script) .collaterals(collateral) // Add single collateral .payTo(recipient, scriptUtxo.output.value) .build(changeTo = changeAddress)

Multiple collateral inputs:

TxBuilder(env) .spend(scriptUtxo, redeemer, script) .collaterals(collateral1, collateral2) .payTo(recipient, scriptUtxo.output.value) .build(changeTo = changeAddress)

When using complete(), collateral inputs are automatically selected and added if the transaction includes script execution. TxBuilder also automatically creates collateral return outputs when needed and respects the protocol’s collateral requirements.

Reference Scripts

CIP-33 reference scripts allow you to store a script in a UTxO and reference it in transactions, reducing transaction size and fees.

Using a Reference Script

When spending a script UTxO, you can reference a script stored in another UTxO instead of including it in the transaction:

// UTxO containing the reference script val refScriptUtxo: Utxo = // ... UTxO with scriptRef field // UTxO locked by the script val scriptUtxo: Utxo = // ... UTxO to spend TxBuilder(env) .references(refScriptUtxo) // Add as reference input .spend(scriptUtxo, redeemer) // Script resolved from reference .collaterals(collateralUtxo) .payTo(recipient, scriptUtxo.output.value) .build(changeTo = changeAddress)

Creating Outputs with Reference Scripts

You can attach a script to an output for others to reference:

import scalus.cardano.ledger.{TransactionOutput, Script} val outputWithScript = TransactionOutput( address = scriptAddress, value = Value.ada(10), datumOption = None, scriptRef = Some(Script.PlutusV3(myScript)) // Attach reference script ) TxBuilder(env) .spend(utxo) .output(outputWithScript) .build(changeTo = changeAddress)

Reference scripts are particularly useful for complex validators, as they significantly reduce transaction fees by not including the full script in each transaction.

Next Steps

Last updated on