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 Issue | Best Practice |
---|---|
Datum/Redeemer Format | Ensure they match validator expectations precisely |
Minimum ADA | Provide sufficient ADA for all outputs |
Collateral | Include appropriate amount of collateral when spending from script addresses |
Signatories | Include all required signers for the validators that require/check them |
Execution Units | Monitor the limits when working with multiple validators |
Reference Scripts | Use 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).