Mesh LogoMesh

Getting Started with Aiken Smart Contracts and Mesh SDK

Build your first Cardano smart contract with Aiken and interact with it using the Mesh TypeScript blockchain SDK for dApp development.

Aiken is a functional programming language designed specifically for Cardano smart contracts. Combined with the Mesh Cardano SDK, you can write, deploy, and interact with Cardano smart contracts using familiar JavaScript Web3 tooling.

This tutorial builds a complete "Hello World" Cardano smart contract—you'll write the on-chain validator in Aiken, then use the Mesh TypeScript blockchain SDK to create transactions that lock and unlock assets in your Cardano dApp development.

Resources:

How Do You Install Aiken?

Linux/MacOS - Use the aikup installer:

curl -sSfL https://install.aiken-lang.org | bash
aikup

All platforms - Install from source via Cargo (requires Rust):

cargo install aiken

Verify your installation:

aiken -V

See the Aiken installation guide for troubleshooting.

How Do You Write an Aiken Smart Contract?

Create a new Aiken project:

aiken meshjs/hello_world
cd hello_world
aiken check

Create validators/hello_world.ak with the validator logic:

use aiken/hash.{Blake2b_224, Hash}
use aiken/list
use aiken/transaction.{ScriptContext}
use aiken/transaction/credential.{VerificationKey}

type Datum {
  owner: Hash<Blake2b_224, VerificationKey>,
}

type Redeemer {
  msg: ByteArray,
}

validator {
  fn hello_world(datum: Datum, redeemer: Redeemer, context: ScriptContext) -> Bool {
    let must_say_hello =
      redeemer.msg == "Hello, World!"

    let must_be_signed =
      list.has(context.transaction.extra_signatories, datum.owner)

    must_say_hello && must_be_signed
  }
}

This validator enforces two conditions:

  1. The redeemer message must be "Hello, World!"
  2. The transaction must be signed by the datum owner

Compile the contract:

aiken build

This generates plutus.json—a CIP-0057 Plutus blueprint containing your compiled validator.

How Do You Lock Assets at the Script Address?

Set up your frontend (see Next.js guide) and install dependencies:

npm install cbor

Copy plutus.json to a data folder and import the required packages:

import {
  resolvePlutusScriptAddress,
  MeshTxBuilder,
  KoiosProvider,
  resolveDataHash,
  resolvePaymentKeyHash,
} from "@meshsdk/core";
import type { PlutusScript, Data } from "@meshsdk/core";
import { CardanoWallet, useWallet } from "@meshsdk/react";

import plutusScript from "../data/plutus.json";
import cbor from "cbor";

Load the compiled contract and resolve its address:

const script: PlutusScript = {
  code: cbor
    .encode(Buffer.from(plutusScript.validators[0].compiledCode, "hex"))
    .toString("hex"),
  version: "V2",
};
const scriptAddress = resolvePlutusScriptAddress(script, 0);

The CBOR encoding converts Aiken's flat format to the format expected by Cardano serialization libraries in the Mesh Cardano SDK.

Build the locking transaction—this sends ADA to the script with a datum containing the owner's public key hash:

const utxos = await wallet.getUtxos();
const changeAddress = await wallet.getChangeAddress();

const hash = resolvePaymentKeyHash((await wallet.getUsedAddresses())[0]);
const datum: Data = {
  alternative: 0,
  fields: [hash],
};

const txBuilder = new MeshTxBuilder({
  fetcher: koios,
});

const unsignedTx = await txBuilder
  .txOut(scriptAddress, [{ unit: "lovelace", quantity: "5000000" }])
  .txOutDatumHashValue(datum)
  .changeAddress(changeAddress)
  .selectUtxosFrom(utxos)
  .complete();

const signedTx = await wallet.signTx(unsignedTx);
const txHash = await wallet.submitTx(signedTx);

How Do You Unlock Assets from the Script?

First, find the UTxO containing your locked assets:

async function _getAssetUtxo({ scriptAddress, asset, datum }) {
  const utxos = await koios.fetchAddressUTxOs(scriptAddress, asset);

  const dataHash = resolveDataHash(datum);

  let utxo = utxos.find((utxo: any) => {
    return utxo.output.dataHash == dataHash;
  });

  return utxo;
}

Build the unlock transaction with the correct redeemer message:

const scriptAddress = resolvePlutusScriptAddress(script, 0);

const address = (await wallet.getUsedAddresses())[0];
const hash = resolvePaymentKeyHash(address);
const datum: Data = {
  alternative: 0,
  fields: [hash],
};

const assetUtxo = await _getAssetUtxo({
  scriptAddress: scriptAddress,
  asset: "lovelace",
  datum: datum,
});

const redeemer: Data = { alternative: 0, fields: ['Hello, World!'] };

const utxos = await wallet.getUtxos();
const changeAddress = await wallet.getChangeAddress();
const collateral = await wallet.getCollateral();

const txBuilder = new MeshTxBuilder({
  fetcher: koios,
});

// create the unlock asset transaction
const unsignedTx = await txBuilder
  .spendingPlutusScriptV2()
  .txIn(assetUtxo.input.txHash, assetUtxo.input.outputIndex)
  .txInDatumValue(datum)
  .txInRedeemerValue(redeemer)
  .txInScript(script.code)
  .txOut(address, assetUtxo.output.amount)
  .requiredSignerHash(hash)
  .txInCollateral(
    collateral[0].input.txHash,
    collateral[0].input.outputIndex,
    collateral[0].output.amount,
    collateral[0].output.address,
  )
  .changeAddress(changeAddress)
  .selectUtxosFrom(utxos)
  .complete();

const signedTx = await wallet.signTx(unsignedTx, true);
const txHash = await wallet.submitTx(signedTx);

Key components of the unlock transaction:

  • spendingPlutusScriptV2() — Indicates a Plutus V2 script spend
  • txInDatumValue — Provides the original datum
  • txInRedeemerValue — Provides "Hello, World!" to satisfy the validator
  • requiredSignerHash — Ensures the owner signs the transaction

What Should You Explore Next?

On this page