Mint with Smart Contract

Simple Smart Contract - Time-locked minting with authorized signer

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 mint purpose 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:

  1. Time-lock: Minting is restricted to transactions before a predefined expiration timestamp

  2. 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:

  1. Mint Array: Defines what tokens to create with their metadata

  2. Script Interactions: Provides the redeemer needed by the validator

  3. Required Signers: Ensures the transaction is signed by the authorized key

  4. 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:

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/submit endpoint.

  • Admin signatures should be applied to the transaction on a secure server.

Both signatures are required for different reasons:

  1. Customer signature: Required because they own the inputs (UTXOs) being used to pay transaction fees

  2. Admin signature: Required by the minting policy's extra_signatories condition

If either signature is missing, the transaction will fail.

Last updated

Was this helpful?