Guides
Simple Smart Contract#
This guide provides a practical implementation of a mint validator. For a general explanation of script purposes and validator types, see Mint Validator.
Objective#
- Implement a simple Aiken smart contract for minting tokens
- Demonstrate the
mintpurpose validator in action - Implement time constraints with
validity_range - Require specific signers with
extra_signatories
Where to Find the Code#
The complete example is available in the Simple Contract repository.
You can follow the README.md for setup instructions.
Smart Contract Overview#
This example implements a minting policy with two validation conditions:
- Time-lock: Minting is restricted to transactions before a predefined expiration timestamp
- Authorized Signer: Only transactions signed by a specific key can mint tokens
The contract is written in Aiken and compiled to a Plutus script that produces a CIP-57 blueprint.
Implementation Example#
Key Components for Minting Policy Transactions#
When building a transaction to mint tokens with a time-locked policy, you need to include:
- Mint Array: Defines what tokens to create with their metadata
- Script Interactions: Provides the redeemer needed by the validator
- Required Signers: Ensures the transaction is signed by the authorized key
- Validity Interval: Sets the transaction time to comply with the time-lock
Complete Implementation#
The following example demonstrates the full process of building, signing, and submitting a transaction that mints tokens with our time-locked policy:
import { Buffer } from "node:buffer";
import { FixedTransaction, PrivateKey } from "npm:@emurgo/cardano-serialization-lib-nodejs";
// Policy hash from your deployed contract blueprint
const policyHash = "eb7bddc5b588e238d2974d544a479b6bc0dc06852b38d12308ac62e5";
// Admin key hash that is authorized to mint (from the contract)
const adminKeyHash = "a2108b5b8f1fb54abdb20c23d8ef4b8303f53bd538e1cefe91335a5d";
// Step 1: Build the transaction input
const input = {
// Address to receive change from the transaction
changeAddress: customer.base_address_preprod,
// Optional transaction message (appears in block explorers)
message: "Minting with time-locked policy",
// Define the token to mint
mint: [
{
version: "cip25",
assetName: "MyToken001",
policyId: policyHash,
type: "plutus", // Indicates a Plutus script-based policy
quantity: 1,
metadata: {
name: "My First Token",
description: "A token with time-lock and signer verification",
image: "ipfs://QmYourImageHash"
},
},
],
// Provide script interaction with redeemer
scriptInteractions: [
{
purpose: "mint",
hash: policyHash,
redeemer: {
type: "hex",
value: "00", // Simple redeemer for this contract (empty array in CBOR)
},
},
],
// Required signer enforced by the policy
requiredSigners: [adminKeyHash],
// Time validity for the time-lock
validityInterval: {
// Current time in seconds (must be before the contract's deadline)
start: Math.floor(Date.now() / 1000)
}
};
// Step 2: Build transaction with Anvil API
const response = await fetch("https://preprod.api.ada-anvil.app/v2/services/transactions/build", {
method: "POST",
headers: { "x-api-key": apiKey, "Content-Type": "application/json" },
body: JSON.stringify(input),
});
const result = await response.json();
const txToSign = result.complete;
const tx = FixedTransaction.from_bytes(Buffer.from(txToSign, "hex"));
// Step 3: Sign with both required keys (multi-signature)
// Customer signature (paying for the transaction)
tx.sign_and_add_vkey_signature(PrivateKey.from_bech32(customer.skey));
// Admin signature (required by the minting policy)
tx.sign_and_add_vkey_signature(PrivateKey.from_bech32(admin.skey));
// Step 4: Submit transaction
const submitted = await fetch("https://preprod.api.ada-anvil.app/v2/services/transactions/submit", {
method: "POST",
headers: { "x-api-key": apiKey, "Content-Type": "application/json" },
body: JSON.stringify({ transaction: tx.to_hex() }),
});
const submittedResponse = await submitted.json();
console.log("Transaction ID:", submittedResponse.hash);Important Technical Details#
Transaction Validation#
The transaction will only succeed if the time-lock and signature conditions are met:
The transaction must be submitted before the contract's deadline timestamp. If the current time exceeds this deadline, validation will fail and the transaction will be rejected.
Signing the Transaction#
This example requires a multi-signature transaction where both the customer wallet and the admin wallet must sign:
Real-World Implementation
In production applications:
- Customer/User signatures would typically be handled in the frontend using wallet connectors like Weld. These connectors will pass the signatures to the
transactions/submitendpoint. - Admin signatures should be applied to the transaction on a secure server.
Both signatures are required for different reasons:
- Customer signature: Required because they own the inputs (UTXOs) being used to pay transaction fees
- Admin signature: Required by the minting policy's
extra_signatoriescondition
If either signature is missing, the transaction will fail.