Documentation
Deep Dives
Interact with Validators
Overview

Introduction

It's important to understand that on the Cardano blockchain, you don't directly interact with "smart contracts", at least not in the traditional sense.

Instead, you work with validators. These validators are responsible for verifying the actions taken in a given transaction, rather than executing or calling any actions themselves. Validators are pure functions that return true or false.

A validator checks whether the transaction meets its requirements, and if it does, the transaction is processed successfully. If the requirements are not met, the transaction fails (is not allowed to execute).

Concepts

Datums are data attached to script UTxOs (like state variables)

const datum = Data.to(new Constr(0, [publicKeyHash]));

You can define datum schemas for type safety:

const DatumSchema = Data.Object({
  owner: Data.Bytes(),
});
type DatumType = Data.Static<typeof DatumSchema>;
const DatumType = DatumSchema as unknown as DatumType;

Workflow

Create a Validator Instance

Lucid consumes compiled validators from languages like PlutusTx, Aiken, or Plutarch:

const spend_val: SpendingValidator = {
  type: "PlutusV3",
  script: "59099a590997010000...", // CBOR format from plutus.json
};
 
const scriptAddress = lucid.utils.validatorToAddress(validator);

Lock Funds at Script Address

const tx = await lucid
  .newTx()
  .pay.ToContract(scriptAddress, { inline: datum }, { lovelace: 10_000_000n })
  .complete();

pay.ToAddressWithData and pay.ToContract are functionally identical. pay.ToContract is just an alias that better expresses the intent when working with smart contracts.

Spend (Redeem) from Script Address

// Find the UTxO we want to spend
const allUTxOs = await lucid.utxosAt(scriptAddress);
const ownerUTxO = allUTxOs.find((utxo) => {
  if (utxo.datum) {
    const datum = Data.from(utxo.datum, DatumType);
    return datum.owner === publicKeyHash;
  }
});
 
// Spend script UTxO
const tx = await lucid
  .newTx()
  .collectFrom([ownerUTxO], redeemer) // Provide the redeemer argument
  .attach.SpendingValidator(spend_val) // Attach validator
  .complete();

Validator Types

Lucid Evolution supports different validator types, each serving a specific purpose:

// Attaching different validator types
.attach.SpendingValidator(validator)    // For spending UTxOs
.attach.MintingPolicy(validator)        // For minting tokens
.attach.CertificateValidator(validator) // For stake operations
.attach.WithdrawalValidator(validator)  // For reward withdrawals
.attach.VoteValidator(validator)        // For governance votes
.attach.ProposeValidator(validator)     // For governance proposals

Corresponding transaction operations:

.collectFrom(utxos, redeemer)           // Spending UTxOs
.mintAssets(assets, redeemer)           // Minting tokens
.delegateTo(stakeAddress, poolId, redeemer) // Stake delegation
.deRegisterStake(stakeAddress, redeemer)    // Stake deregistration
.withdraw(stakeAddress, rewardAmount, redeemer) // Reward withdrawal

Troubleshooting & Best Practices

Common IssueBest Practice
Datum/Redeemer FormatEnsure they match validator expectations precisely
Minimum ADAProvide sufficient ADA for all outputs
CollateralInclude appropriate amount of collateral when spending from script addresses
SignatoriesInclude all required signers for the validators that require/check them
Execution UnitsMonitor the limits when working with multiple validators
Reference ScriptsUse reference scripts when possible to significantly reduce transaction costs

For a comprehensive documentation of validators, see the official Aiken documentation (opens in a new tab).