Skip to main content

Smart Contract Templates

Pre-built smart contract templates for common use cases. Generate contract configurations, datums, and transaction builders without writing Plutus code.

Overview

Cardano DevKit provides templates for:

  • Escrow - Two-party agreements with optional arbiter
  • Vesting - Token release schedules
  • Time-Lock - Assets locked until a specific time
  • Multi-Party - Agreements requiring multiple signers
  • Token Sale - ICO/IDO token sales
  • Auction - English and Dutch auctions
  • Royalty - CIP-27 royalty distribution
  • Rewards Distribution - Staking rewards sharing

Installation

import {
// Templates
EscrowTemplate,
VestingTemplate,
TimeLockTemplate,
MultiPartyTemplate,
TokenSaleTemplate,
AuctionTemplate,
RoyaltyTemplate,
RewardsDistributionTemplate,
// Factory Functions
createEscrowTemplate,
createVestingTemplate,
createTimeLockTemplate,
createMultiPartyTemplate,
createTokenSaleTemplate,
createAuctionTemplate,
createRoyaltyTemplate,
createRewardsDistributionTemplate,
// Utilities
dateToSlot,
slotToDate,
validateRoyalty,
calculateRoyalty,
generateScriptHashPlaceholder,
} from 'cardano-devkit';

Escrow Template

Two-party escrow with optional arbiter for dispute resolution.

Configuration

interface EscrowConfig {
seller: string; // Seller's address
buyer: string; // Buyer's address
arbiter?: string; // Optional arbiter address
amount: bigint; // Escrow amount in lovelace
deadline: number; // Unix timestamp deadline
requireArbiter?: boolean; // Whether arbiter approval is required
autoRefundOnDeadline?: boolean; // Auto-refund if deadline passes
}

Usage

import { createEscrowTemplate, EscrowState } from 'cardano-devkit';

const escrow = createEscrowTemplate({
seller: "addr_test1seller...",
buyer: "addr_test1buyer...",
arbiter: "addr_test1arbiter...",
amount: 100_000_000n, // 100 ADA
deadline: Date.now() + 7 * 24 * 60 * 60 * 1000, // 7 days
requireArbiter: true,
});

// Build contract
const contract = escrow.build();
console.log("Script address:", contract.scriptAddress);
console.log("Script hash:", contract.scriptHash);

// Generate datum for contract state
const pendingDatum = escrow.getDatum(EscrowState.Pending);
const releasedDatum = escrow.getDatum(EscrowState.Released);
const refundedDatum = escrow.getDatum(EscrowState.Refunded);

// Create release transaction (buyer confirms receipt)
const releaseTx = escrow.createReleaseTransaction(lucid, escrowUtxo);

// Create refund transaction (seller refunds buyer)
const refundTx = escrow.createRefundTransaction(lucid, escrowUtxo);

// Check if deadline has passed
const isPastDeadline = escrow.isDeadlinePassed();

// Get remaining time
const remainingMs = escrow.getRemainingTime();

Escrow States

enum EscrowState {
Pending = 'pending', // Initial state, awaiting release or refund
Released = 'released', // Funds released to seller
Refunded = 'refunded', // Funds refunded to buyer
Disputed = 'disputed', // Arbiter intervention needed
}

Vesting Template

Token vesting with customizable release schedules.

Configuration

interface VestingConfig {
beneficiary: string; // Who receives the tokens
totalAmount: bigint; // Total amount to vest
schedule: VestingScheduleEntry[];
cliffPeriod?: number; // Optional cliff period in ms
revocable?: boolean; // Can the vesting be revoked?
revoker?: string; // Who can revoke (if revocable)
}

interface VestingScheduleEntry {
unlockTime: number; // Unix timestamp when this portion unlocks
amount: bigint; // Amount that unlocks at this time
}

Usage

import { createVestingTemplate } from 'cardano-devkit';

const vesting = createVestingTemplate({
beneficiary: "addr_test1beneficiary...",
totalAmount: 1_000_000_000n, // 1000 ADA total
schedule: [
{ unlockTime: Date.now() + 30 * 24 * 60 * 60 * 1000, amount: 250_000_000n }, // 25% at 30 days
{ unlockTime: Date.now() + 60 * 24 * 60 * 60 * 1000, amount: 250_000_000n }, // 25% at 60 days
{ unlockTime: Date.now() + 90 * 24 * 60 * 60 * 1000, amount: 500_000_000n }, // 50% at 90 days
],
cliffPeriod: 30 * 24 * 60 * 60 * 1000, // 30 day cliff
});

// Build contract
const contract = vesting.build();

// Get currently vested amount
const vested = vesting.getVestedAmount();
console.log("Currently vested:", vested);

// Get next unlock
const nextUnlock = vesting.getNextUnlockTime();
console.log("Next unlock at:", new Date(nextUnlock));

// Get all future unlocks
const unlocks = vesting.getUnlockSchedule();
for (const unlock of unlocks) {
console.log(`${unlock.amount} unlocks at ${new Date(unlock.unlockTime)}`);
}

// Create claim transaction
const claimTx = vesting.createClaimTransaction(lucid, vestingUtxo);

// Check if cliff has passed
const cliffPassed = vesting.isCliffPassed();

Time-Lock Template

Lock assets until a specific time.

Configuration

interface TimeLockConfig {
owner: string; // Who can unlock after time passes
unlockTime: number; // Unix timestamp when unlock is allowed
assets?: { // Optional: lock specific assets
lovelace?: bigint;
tokens?: Record<string, bigint>;
};
}

Usage

import { createTimeLockTemplate } from 'cardano-devkit';

const timeLock = createTimeLockTemplate({
owner: "addr_test1owner...",
unlockTime: Date.now() + 365 * 24 * 60 * 60 * 1000, // 1 year
});

// Build contract
const contract = timeLock.build();

// Check if unlocked
const isUnlocked = timeLock.isUnlocked();

// Get time until unlock
const timeRemaining = timeLock.getTimeUntilUnlock();
console.log("Time remaining:", formatDuration(timeRemaining));

// Create unlock transaction (only works after unlockTime)
if (isUnlocked) {
const unlockTx = timeLock.createUnlockTransaction(lucid, lockUtxo);
}

Multi-Party Template

Agreements requiring multiple parties to sign.

Configuration

interface MultiPartyConfig {
parties: string[]; // Array of party addresses
requiredSignatures: number; // How many must sign (M-of-N)
deadline?: number; // Optional deadline
description?: string; // Contract description
}

Usage

import { createMultiPartyTemplate } from 'cardano-devkit';

// 2-of-3 multi-sig
const multiParty = createMultiPartyTemplate({
parties: [
"addr_test1alice...",
"addr_test1bob...",
"addr_test1carol...",
],
requiredSignatures: 2,
deadline: Date.now() + 30 * 24 * 60 * 60 * 1000,
});

// Build contract
const contract = multiParty.build();

// Get required signers
const required = multiParty.getRequiredSignatures();
const total = multiParty.getTotalParties();
console.log(`Requires ${required} of ${total} signatures`);

// Create execution transaction
const executeTx = multiParty.createExecuteTransaction(lucid, utxo);

// Verify signers
const isValid = multiParty.hasRequiredSignatures(signatures);

Token Sale Template

ICO/IDO style token sales.

Configuration

interface TokenSaleConfig {
seller: string; // Token seller's address
tokenPolicyId: string; // Policy ID of tokens being sold
tokenAssetName: string; // Asset name of tokens
pricePerToken: bigint; // Price per token in lovelace
totalTokens: bigint; // Total tokens for sale
startTime: number; // Sale start time
endTime: number; // Sale end time
minPurchase?: bigint; // Minimum purchase amount
maxPurchase?: bigint; // Maximum purchase amount per wallet
softCap?: bigint; // Soft cap (refund if not met)
hardCap?: bigint; // Hard cap (sale ends when reached)
whitelist?: string[]; // Optional whitelist of addresses
}

Usage

import { createTokenSaleTemplate } from 'cardano-devkit';

const sale = createTokenSaleTemplate({
seller: "addr_test1seller...",
tokenPolicyId: "abc123...",
tokenAssetName: "MyToken",
pricePerToken: 1_000_000n, // 1 ADA per token
totalTokens: 1_000_000n,
startTime: Date.now() + 24 * 60 * 60 * 1000, // Starts in 24 hours
endTime: Date.now() + 7 * 24 * 60 * 60 * 1000, // Ends in 7 days
minPurchase: 10n,
maxPurchase: 10_000n,
});

// Build contract
const contract = sale.build();

// Check sale status
const isActive = sale.isSaleActive();
const hasStarted = sale.hasStarted();
const hasEnded = sale.hasEnded();

// Get remaining tokens
const remaining = sale.getRemainingTokens();

// Calculate cost
const cost = sale.calculateCost(100n); // Cost for 100 tokens
console.log("Cost for 100 tokens:", cost);

// Check if purchase is valid
const isValid = sale.validatePurchase("addr_test1buyer...", 100n);

// Create purchase transaction
const purchaseTx = sale.createPurchaseTransaction(lucid, buyerAddress, 100n);

// Get sale progress
const progress = sale.getSaleProgress();
console.log(`${progress.sold} of ${progress.total} sold (${progress.percentage}%)`);

Auction Template

English (ascending) or Dutch (descending) auctions.

Configuration

interface AuctionConfig {
seller: string; // Seller's address
startingBid: bigint; // Starting bid amount
minBidIncrement?: bigint; // Minimum bid increment
startTime: number; // Auction start time
endTime: number; // Auction end time
auctionType: 'english' | 'dutch';

// Dutch auction specific
endingBid?: bigint; // Final price (for Dutch auctions)
priceDropInterval?: number; // How often price drops (ms)

// Reserve price
reservePrice?: bigint; // Minimum price to sell

// Asset being auctioned
asset?: {
policyId: string;
assetName: string;
amount: bigint;
};
}

Usage

import { createAuctionTemplate } from 'cardano-devkit';

// English auction
const englishAuction = createAuctionTemplate({
seller: "addr_test1seller...",
startingBid: 10_000_000n,
minBidIncrement: 1_000_000n,
startTime: Date.now(),
endTime: Date.now() + 24 * 60 * 60 * 1000, // 24 hours
auctionType: 'english',
reservePrice: 50_000_000n,
});

// Dutch auction
const dutchAuction = createAuctionTemplate({
seller: "addr_test1seller...",
startingBid: 100_000_000n, // Start high
endingBid: 10_000_000n, // End low
priceDropInterval: 60 * 60 * 1000, // Drop every hour
startTime: Date.now(),
endTime: Date.now() + 24 * 60 * 60 * 1000,
auctionType: 'dutch',
});

// Build contract
const contract = englishAuction.build();

// Check auction status
const isActive = englishAuction.isAuctionActive();
const hasEnded = englishAuction.hasAuctionEnded();

// Get current price (Dutch) or minimum bid (English)
const currentPrice = englishAuction.getCurrentPrice();
const minBid = englishAuction.getMinimumBid(currentHighestBid);

// Place bid
const bidTx = englishAuction.createBidTransaction(lucid, utxo, bidAmount);

// Finalize auction (after end time)
const finalizeTx = englishAuction.createFinalizeTransaction(lucid, utxo);

// Get auction info
const info = englishAuction.getAuctionInfo();
console.log("Current highest bid:", info.highestBid);
console.log("Highest bidder:", info.highestBidder);
console.log("Time remaining:", info.timeRemaining);

Royalty Template

CIP-27 compliant royalty distribution.

Configuration

interface RoyaltyConfig {
creator: string; // Original creator's address
royaltyPercent: number; // Royalty percentage (0-100)
recipients?: Array<{ // Split royalties among multiple recipients
address: string;
percent: number;
}>;
minRoyalty?: bigint; // Minimum royalty amount
}

Usage

import { createRoyaltyTemplate, validateRoyalty, calculateRoyalty } from 'cardano-devkit';

const royalty = createRoyaltyTemplate({
creator: "addr_test1creator...",
royaltyPercent: 5, // 5% royalty
recipients: [
{ address: "addr_test1artist...", percent: 80 }, // 80% of royalty to artist
{ address: "addr_test1platform...", percent: 20 }, // 20% to platform
],
});

// Build royalty info
const info = royalty.build();

// Calculate royalty for a sale
const salePrice = 100_000_000n; // 100 ADA
const royaltyAmount = calculateRoyalty(salePrice, 5);
console.log("Royalty amount:", royaltyAmount); // 5_000_000n

// Get distribution
const distribution = royalty.getDistribution(royaltyAmount);
// { "addr_test1artist...": 4_000_000n, "addr_test1platform...": 1_000_000n }

// Validate royalty percentage
const isValid = validateRoyalty(5); // true
const isInvalid = validateRoyalty(101); // false

// Create royalty payment transaction
const paymentTx = royalty.createRoyaltyPaymentTransaction(lucid, salePrice);

Rewards Distribution Template

Distribute staking rewards or other earnings.

Configuration

interface RewardsDistributionConfig {
distributor: string; // Who distributes rewards
recipients: Array<{ // Recipients and their shares
address: string;
share: number; // Share as percentage (0-100)
}>;
minDistribution?: bigint; // Minimum amount to distribute
vestingPeriod?: number; // Optional vesting for rewards
}

Usage

import { createRewardsDistributionTemplate } from 'cardano-devkit';

const rewards = createRewardsDistributionTemplate({
distributor: "addr_test1treasury...",
recipients: [
{ address: "addr_test1staker1...", share: 40 },
{ address: "addr_test1staker2...", share: 35 },
{ address: "addr_test1staker3...", share: 25 },
],
minDistribution: 10_000_000n, // Only distribute if >= 10 ADA
});

// Build contract
const contract = rewards.build();

// Calculate individual distributions
const totalRewards = 100_000_000n;
const distribution = rewards.calculateDistribution(totalRewards);
// Returns array of { address, amount }

// Create distribution transaction
const distributeTx = rewards.createDistributionTransaction(lucid, totalRewards);

// Validate shares add up to 100%
const isValid = rewards.validateShares();

Utility Functions

dateToSlot

Convert JavaScript Date to Cardano slot number.

import { dateToSlot } from 'cardano-devkit';

const slot = dateToSlot(new Date('2024-12-31'), 'Preprod');

slotToDate

Convert Cardano slot to JavaScript Date.

import { slotToDate } from 'cardano-devkit';

const date = slotToDate(12345678, 'Preprod');

validateRoyalty

Validate royalty percentage.

import { validateRoyalty } from 'cardano-devkit';

validateRoyalty(5); // true (valid 0-100)
validateRoyalty(150); // false (invalid)
validateRoyalty(-1); // false (invalid)

calculateRoyalty

Calculate royalty amount from sale price.

import { calculateRoyalty } from 'cardano-devkit';

const royalty = calculateRoyalty(100_000_000n, 5); // 5% of 100 ADA = 5 ADA

generateScriptHashPlaceholder

Generate a placeholder script hash for testing.

import { generateScriptHashPlaceholder } from 'cardano-devkit';

const hash = generateScriptHashPlaceholder('my-contract');
// Returns consistent hash for testing

Best Practices

  1. Always validate configurations - Use the template's validation methods before building
  2. Check deadlines - Verify time-based conditions before creating transactions
  3. Test on devnet first - Use local devnet to test contract interactions
  4. Handle all states - Account for all possible contract states in your UI
  5. Use factory functions - createEscrowTemplate() is more readable than new EscrowTemplate()
  6. Monitor expiration - Set up alerts for approaching deadlines

Integration Example

Complete example integrating multiple templates:

import {
createDevKit,
createEscrowTemplate,
createVestingTemplate,
createRoyaltyTemplate,
EscrowState,
} from 'cardano-devkit';

async function nftMarketplaceSale() {
const devKit = createDevKit({ network: 'Preprod' });
const lucid = await devKit.init();

// Set up escrow for the sale
const escrow = createEscrowTemplate({
seller: sellerAddress,
buyer: buyerAddress,
amount: 100_000_000n,
deadline: Date.now() + 7 * 24 * 60 * 60 * 1000,
});

// Set up royalty distribution
const royalty = createRoyaltyTemplate({
creator: artistAddress,
royaltyPercent: 10,
});

// Set up vesting for seller's proceeds
const vesting = createVestingTemplate({
beneficiary: sellerAddress,
totalAmount: 90_000_000n, // After royalty
schedule: [
{ unlockTime: Date.now() + 30 * 24 * 60 * 60 * 1000, amount: 45_000_000n },
{ unlockTime: Date.now() + 60 * 24 * 60 * 60 * 1000, amount: 45_000_000n },
],
});

// Execute sale flow
// 1. Create escrow
// 2. On release, distribute royalty
// 3. Lock remaining in vesting
}