Skip to main content

Aiken Smart Contracts

Cardano DevKit provides comprehensive support for Aiken smart contract development.

Overview

import {
AikenHelper,
loadContract,
loadAllContracts,
applyParams,
getContractInfo,
listValidators,
} from 'cardano-devkit';

Setup Guide

AikenHelper.printSetupGuide()

Print Aiken installation instructions.

AikenHelper.printSetupGuide();

AikenHelper.getSetupInstructions()

Get setup instructions as a string array.

const instructions = AikenHelper.getSetupInstructions();
instructions.forEach(line => console.log(line));

Loading Contracts

loadContract(plutusJson, validatorTitle)

Load a specific validator from a compiled Aiken project.

import plutusJson from './plutus.json';

const vesting = loadContract(plutusJson, 'vesting.vesting');

console.log("Hash:", vesting.hash);
console.log("Type:", vesting.scriptType);
console.log("Address:", vesting.address);

// Use with Lucid
const tx = await lucid.newTx()
.attachSpendingValidator(vesting.script)
.complete();

Returns:

interface LoadedContract {
title: string;
hash: string;
scriptType: 'PlutusV1' | 'PlutusV2' | 'PlutusV3';
script: Script; // Lucid-compatible script
address: string; // Bech32 address
compiledCode: string;
params?: AikenParameter[];
}

loadAllContracts(plutusJson)

Load all validators from a compiled Aiken project.

import plutusJson from './plutus.json';

const contracts = loadAllContracts(plutusJson);

for (const [name, contract] of Object.entries(contracts)) {
console.log(`${name}:`);
console.log(` Hash: ${contract.hash}`);
console.log(` Address: ${contract.address}`);
}

listValidators(plutusJson)

List all available validators.

const validators = listValidators(plutusJson);
// ['vesting.vesting', 'escrow.escrow', 'nft.mint']

Parameterized Contracts

applyParams(contract, params)

Apply parameters to a parameterized validator.

const vestingTemplate = loadContract(plutusJson, 'vesting.vesting');

// Apply parameters
const vestingInstance = applyParams(vestingTemplate, [
"addr_test1...", // beneficiary
1234567890n, // deadline
]);

console.log("Instance Hash:", vestingInstance.hash);
console.log("Instance Address:", vestingInstance.address);

// Each parameter set creates a unique script hash
const instance2 = applyParams(vestingTemplate, [
"addr_test2...",
1234567999n,
]);

console.log(vestingInstance.hash !== instance2.hash); // true

Contract Information

getContractInfo(contract)

Get detailed information about a loaded contract.

const contract = loadContract(plutusJson, 'vesting.vesting');
const info = getContractInfo(contract);

console.log("Title:", info.title);
console.log("Script Type:", info.scriptType);
console.log("Hash:", info.hash);
console.log("Address:", info.address);
console.log("Parameters:", info.params);
console.log("Has Datum:", info.hasDatum);
console.log("Has Redeemer:", info.hasRedeemer);

Parsing Plutus JSON

AikenHelper.parseValidators(plutusJson)

Parse validators from plutus.json with basic information.

import plutusJson from './plutus.json';

const validators = AikenHelper.parseValidators(plutusJson);

for (const validator of validators) {
console.log(`Validator: ${validator.title}`);
console.log(` Hash: ${validator.hash}`);
console.log(` Type: ${validator.scriptType}`);
console.log(` Compiled Code: ${validator.compiledCode.substring(0, 50)}...`);
}

AikenHelper.getInfo()

Get Aiken CLI information.

const info = AikenHelper.getInfo();
console.log("Aiken Version:", info.version);
console.log("Documentation:", info.docsUrl);

Complete Example

import { Lucid, Blockfrost } from '@lucid-evolution/lucid';
import {
loadContract,
applyParams,
createSmartContractDebugger,
} from 'cardano-devkit';
import plutusJson from './plutus.json';

async function deployVesting() {
// Initialize Lucid
const lucid = await Lucid.new(
new Blockfrost("https://cardano-preprod.blockfrost.io/api", "your-api-key"),
"Preprod"
);

// Load wallet
lucid.selectWallet.fromSeed("your seed phrase...");
const myAddress = await lucid.wallet().address();

// Load and parameterize the vesting contract
const vestingTemplate = loadContract(plutusJson, 'vesting.vesting');

const deadline = BigInt(Date.now() + 86400000); // 24 hours from now
const vesting = applyParams(vestingTemplate, [
myAddress, // beneficiary
deadline, // deadline
]);

console.log("Vesting Contract:");
console.log(" Hash:", vesting.hash);
console.log(" Address:", vesting.address);

// Create debugger for testing
const debug = createSmartContractDebugger({
scriptType: vesting.scriptType,
scriptHash: vesting.hash,
});

// Lock funds at the vesting contract
const datum = {
beneficiary: myAddress,
deadline: deadline,
};

const lockTx = await lucid.newTx()
.pay.ToContract(
vesting.address,
{ kind: "inline", value: Data.to(datum) },
{ lovelace: 10000000n }
)
.complete();

const signedLockTx = await lockTx.sign.withWallet().complete();
const lockTxHash = await signedLockTx.submit();

console.log("Funds locked:", lockTxHash);

// Later, claim the funds (after deadline)
const utxos = await lucid.utxosAt(vesting.address);

const claimTx = await lucid.newTx()
.collectFrom(utxos, Data.to({ action: "Claim" }))
.attachSpendingValidator(vesting.script)
.validFrom(Date.now())
.complete();

const signedClaimTx = await claimTx.sign.withWallet().complete();
const claimTxHash = await signedClaimTx.submit();

console.log("Funds claimed:", claimTxHash);
}

deployVesting().catch(console.error);

Aiken Project Structure

A typical Aiken project structure:

my-validator/
├── aiken.toml # Project configuration
├── lib/ # Library modules
│ └── my_validator/
│ └── types.ak # Custom types
├── validators/ # Validator scripts
│ ├── vesting.ak # Vesting validator
│ └── escrow.ak # Escrow validator
└── plutus.json # Compiled output (after `aiken build`)

Sample Aiken Validator

// validators/vesting.ak

use aiken/transaction.{ScriptContext}
use aiken/interval

type Datum {
beneficiary: Address,
deadline: POSIXTime,
}

type Redeemer {
Claim
Cancel
}

validator vesting {
spend(datum: Datum, redeemer: Redeemer, ctx: ScriptContext) {
when redeemer is {
Claim -> {
let must_be_signed =
list.has(ctx.transaction.extra_signatories, datum.beneficiary)
let must_be_after_deadline =
interval.is_entirely_after(ctx.transaction.validity_range, datum.deadline)
must_be_signed && must_be_after_deadline
}
Cancel -> False // Add cancel logic
}
}
}

Best Practices

  1. Always test locally first using the local devnet before deploying to testnets
  2. Use parameterized contracts for flexibility instead of hardcoding values
  3. Debug with SmartContractDebugger to catch issues early
  4. Keep plutus.json in version control for reproducible deployments
  5. Validate datum and redeemer structures match your Aiken types
  6. Check script sizes to ensure they fit within protocol limits