Write a Smart Contract
Create your first Aiken validator with datum and redeemer validation
Overview
In this guide, you create an Aiken validator that locks assets on-chain. The validator requires a specific message ("Hello, World!") and the owner's signature to unlock the funds.
What you'll build:
- A validator with custom datum (owner's key hash)
- Redeemer validation (message check)
- Transaction signature verification
Prerequisites:
- Aiken CLI installed
- Basic understanding of Cardano transactions
Quick Start
Create and compile a validator in 3 steps.
aiken new meshjs/hello_world
cd hello_worldCreate validators/hello_world.ak:
use aiken/collection/list
use aiken/crypto.{VerificationKeyHash}
use cardano/transaction.{ScriptContext}
pub type Datum {
owner: VerificationKeyHash,
}
pub type Redeemer {
msg: ByteArray,
}
validator hello_world {
spend(
datum: Option<Datum>,
redeemer: Redeemer,
_own_ref: Data,
tx: transaction.Transaction,
) {
expect Some(d) = datum
let must_say_hello = redeemer.msg == "Hello, World!"
let must_be_signed = list.has(tx.extra_signatories, d.owner)
must_say_hello && must_be_signed
}
}aiken buildStep-by-Step Guide
Step 1: Create a New Project
Initialize an Aiken project with the standard structure.
aiken new meshjs/hello_world
cd hello_worldVerify the project was created:
aiken checkStep 2: Define Your Types
Create a new file validators/hello_world.ak. Start by defining the datum and redeemer types.
use aiken/collection/list
use aiken/crypto.{VerificationKeyHash}
use cardano/transaction.{ScriptContext}
/// The datum stores the owner's verification key hash.
/// Only the owner can unlock funds from this script.
pub type Datum {
owner: VerificationKeyHash,
}
/// The redeemer contains the message that must match "Hello, World!"
pub type Redeemer {
msg: ByteArray,
}Key points:
VerificationKeyHashis a 28-byte hash of a public key- Custom types make your contract self-documenting
- Comments with
///appear in generated documentation
Step 3: Write the Validator Logic
Add the validator function that checks the unlock conditions.
validator hello_world {
spend(
datum: Option<Datum>,
redeemer: Redeemer,
_own_ref: Data,
tx: transaction.Transaction,
) {
// Unwrap the datum - fail if None
expect Some(d) = datum
// Check 1: Redeemer message must be "Hello, World!"
let must_say_hello = redeemer.msg == "Hello, World!"
// Check 2: Transaction must be signed by the owner
let must_be_signed = list.has(tx.extra_signatories, d.owner)
// Both conditions must be true
must_say_hello && must_be_signed
}
}What this validator does:
- Extracts the datum containing the owner's key hash
- Verifies the redeemer message equals "Hello, World!"
- Confirms the transaction is signed by the owner
- Returns
Trueonly if both conditions pass
Step 4: Compile the Contract
Build the project to generate the Plutus blueprint.
aiken buildThis creates plutus.json in the project root. The blueprint contains:
{
"validators": [
{
"title": "hello_world.hello_world.spend",
"compiledCode": "5901a2...",
"hash": "abc123..."
}
]
}Step 5: Verify with Type Checking
Run the type checker to ensure your code is valid.
aiken checkIf there are errors, fix them before proceeding. Common issues:
- Missing imports
- Type mismatches in datum/redeemer
- Incorrect function signatures
Complete Validator Code
Here's the full validators/hello_world.ak file:
use aiken/collection/list
use aiken/crypto.{VerificationKeyHash}
use cardano/transaction.{ScriptContext}
/// The datum stores the owner's verification key hash.
/// Only the owner can unlock funds from this script.
pub type Datum {
owner: VerificationKeyHash,
}
/// The redeemer contains the unlock message.
pub type Redeemer {
msg: ByteArray,
}
/// A simple validator that requires:
/// 1. The redeemer message to be "Hello, World!"
/// 2. The transaction to be signed by the datum owner
validator hello_world {
spend(
datum: Option<Datum>,
redeemer: Redeemer,
_own_ref: Data,
tx: transaction.Transaction,
) {
expect Some(d) = datum
let must_say_hello = redeemer.msg == "Hello, World!"
let must_be_signed = list.has(tx.extra_signatories, d.owner)
must_say_hello && must_be_signed
}
}Using the Compiled Script with Mesh
After building, integrate the compiled script with your TypeScript application.
import {
applyParamsToScript,
resolvePlutusScriptAddress,
PlutusScript,
} from "@meshsdk/core";
import blueprint from "./hello_world/plutus.json";
// Get the compiled code from the blueprint
const compiledCode = blueprint.validators[0].compiledCode;
// Apply parameters (none in this case, but required for CBOR encoding)
const scriptCbor = applyParamsToScript(compiledCode, []);
// Create the script object
const script: PlutusScript = {
code: scriptCbor,
version: "V3",
};
// Resolve the script address (0 = mainnet, 1 = preprod/preview)
const scriptAddress = resolvePlutusScriptAddress(script, 0);
console.log("Script Address:", scriptAddress);Troubleshooting
aiken build fails
Check for syntax errors with aiken check. Common issues:
// Wrong: Missing comma
pub type Datum {
owner: VerificationKeyHash
name: ByteArray // <- Missing comma
}
// Correct
pub type Datum {
owner: VerificationKeyHash,
name: ByteArray,
}Validator returns False unexpectedly
Debug by testing each condition separately:
validator hello_world {
spend(datum: Option<Datum>, redeemer: Redeemer, _own_ref: Data, tx: transaction.Transaction) {
expect Some(d) = datum
// Debug: Log which condition fails
trace @"Checking message..."
let must_say_hello = redeemer.msg == "Hello, World!"
trace @"Checking signature..."
let must_be_signed = list.has(tx.extra_signatories, d.owner)
must_say_hello && must_be_signed
}
}Import errors
Ensure you import from the correct modules:
// Correct imports for Aiken v1.1+
use aiken/collection/list
use aiken/crypto.{VerificationKeyHash}
use cardano/transaction.{ScriptContext}Related Links
- Build Transactions - Lock and unlock assets with this validator
- Getting Started - Installation and CLI reference
- Aiken Language Tour - Complete syntax reference
- Official Hello World Example - Aiken's tutorial