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
- Always test locally first using the local devnet before deploying to testnets
- Use parameterized contracts for flexibility instead of hardcoding values
- Debug with SmartContractDebugger to catch issues early
- Keep plutus.json in version control for reproducible deployments
- Validate datum and redeemer structures match your Aiken types
- Check script sizes to ensure they fit within protocol limits