Mesh LogoMesh

NFT Minting Machine

Mint NFTs with auto-incrementing indexes using an on-chain oracle

The NFT Minting Machine contract enables you to mint NFTs with automatically incrementing indexes. Each new NFT gets the next number in sequence, tracked by an on-chain oracle. This is ideal for collections where you want numbered editions like "Mesh Token #1", "Mesh Token #2", etc.

Use Cases

  • Numbered NFT collections
  • Sequential edition minting
  • Limited series with verifiable order
  • Collection drops with provable mint order
  • Membership cards with sequential IDs

Quick Start

Install the Package

npm install @meshsdk/contract @meshsdk/core

Setup Overview

The NFT Minting Machine requires a three-step setup:

StepActionPurpose
1Setup OracleCreate the one-time minting policy and oracle token
2Save paramUtxoStore the oracle reference for future mints
3Mint NFTsUse the oracle to mint sequentially numbered NFTs

Contract Logic

The contract uses an oracle pattern to track minting state:

ComponentDescription
Oracle TokenA one-time minted NFT that holds the collection state
Oracle DatumStores the current NFT index and collection info
Minting PolicyEnsures each NFT is numbered correctly

How It Works

  1. Setup: Mint an oracle token that stores nftIndex = 0
  2. First Mint: Oracle shows index 0, mint NFT #0, update oracle to index 1
  3. Second Mint: Oracle shows index 1, mint NFT #1, update oracle to index 2
  4. And so on: Each mint increments the oracle index

Available Actions

Setup Oracle

Initialize the minting machine by creating the oracle token. This is a one-time setup.

Method Signature

contract.setupOracle(priceLovelace: number): Promise<{ tx: string; paramUtxo: { outputIndex: number; txHash: string } }>

Parameters

ParameterTypeDescription
priceLovelacenumberPrice per NFT in lovelace

Code Example

import { MeshPlutusNFTContract } from "@meshsdk/contract";
import { BlockfrostProvider, MeshTxBuilder } from "@meshsdk/core";

// Initialize provider
const provider = new BlockfrostProvider("<Your-API-Key>");

const meshTxBuilder = new MeshTxBuilder({
  fetcher: provider,
  submitter: provider,
  verbose: true,
});

// Initialize contract with collection name
const contract = new MeshPlutusNFTContract(
  {
    mesh: meshTxBuilder,
    fetcher: provider,
    wallet: wallet,
    networkId: 0,
  },
  {
    collectionName: "MeshCollection",
  }
);

// Setup oracle with 10 ADA mint price
const { tx, paramUtxo } = await contract.setupOracle(10000000);
const signedTx = await wallet.signTx(tx);
const txHash = await wallet.submitTx(signedTx);

console.log("Oracle setup complete. Transaction hash:", txHash);
console.log("IMPORTANT - Save this paramUtxo:", JSON.stringify(paramUtxo));
// Example: {"outputIndex":0,"txHash":"63dbd563ee9979574401599a42841e0d5b63a691af95df863cbf37d5cb44a558"}

What Happens on Success

  • Oracle token is minted
  • Collection state is initialized with index 0
  • You receive paramUtxo - save this for all future mints

Mint NFT

Mint a new NFT with the next sequential index.

Method Signature

contract.mintPlutusNFT(assetMetadata: object): Promise<string>

Parameters

ParameterTypeDescription
assetMetadataobjectCIP-25 compliant NFT metadata

Code Example

import { MeshPlutusNFTContract } from "@meshsdk/contract";
import { BlockfrostProvider, MeshTxBuilder } from "@meshsdk/core";

// Initialize provider
const provider = new BlockfrostProvider("<Your-API-Key>");

const meshTxBuilder = new MeshTxBuilder({
  fetcher: provider,
  submitter: provider,
  verbose: true,
});

// Initialize contract with collection name AND paramUtxo from setup
const contract = new MeshPlutusNFTContract(
  {
    mesh: meshTxBuilder,
    fetcher: provider,
    wallet: wallet,
    networkId: 0,
  },
  {
    collectionName: "MeshCollection",
    paramUtxo: {
      outputIndex: 0,
      txHash: "63dbd563ee9979574401599a42841e0d5b63a691af95df863cbf37d5cb44a558",
    },
  }
);

// Get current index from oracle
const oracleData = await contract.getOracleData();

// Define NFT metadata with current index
const assetMetadata = {
  name: `Mesh Token ${oracleData.nftIndex}`,
  image: "ipfs://QmRzicpReutwCkM6aotuKjErFCUD213DpwPq6ByuzMJaua",
  description: `Mesh NFT Collection - Edition #${oracleData.nftIndex}`,
  attributes: [
    { trait_type: "Edition", value: oracleData.nftIndex },
    { trait_type: "Collection", value: "MeshCollection" },
  ],
};

// Mint the NFT
const tx = await contract.mintPlutusNFT(assetMetadata);
const signedTx = await wallet.signTx(tx);
const txHash = await wallet.submitTx(signedTx);

console.log(`Minted NFT #${oracleData.nftIndex}. Transaction hash:`, txHash);

What Happens on Success

  • New NFT is minted with the current index
  • Oracle index increments by 1
  • NFT is sent to the minter's wallet
  • Minting price (if set) is collected

Get Oracle Data

Fetch the current oracle state including the next NFT index.

Method Signature

contract.getOracleData(): Promise<{ nftIndex: number; price: number }>

Code Example

import { MeshPlutusNFTContract } from "@meshsdk/contract";
import { BlockfrostProvider, MeshTxBuilder } from "@meshsdk/core";

// Initialize provider
const provider = new BlockfrostProvider("<Your-API-Key>");

const meshTxBuilder = new MeshTxBuilder({
  fetcher: provider,
  submitter: provider,
  verbose: true,
});

// Initialize contract with paramUtxo
const contract = new MeshPlutusNFTContract(
  {
    mesh: meshTxBuilder,
    fetcher: provider,
    wallet: wallet,
    networkId: 0,
  },
  {
    collectionName: "MeshCollection",
    paramUtxo: {
      outputIndex: 0,
      txHash: "63dbd563ee9979574401599a42841e0d5b63a691af95df863cbf37d5cb44a558",
    },
  }
);

// Get oracle data
const oracleData = await contract.getOracleData();

console.log("Current NFT Index:", oracleData.nftIndex);
console.log("Mint Price:", oracleData.price / 1000000, "ADA");

Return Value

{
  nftIndex: 5,      // Next NFT will be #5
  price: 10000000   // 10 ADA mint price
}

Full Working Example

import { MeshPlutusNFTContract } from "@meshsdk/contract";
import { BlockfrostProvider, MeshTxBuilder } from "@meshsdk/core";
import { MeshCardanoBrowserWallet } from "@meshsdk/wallet";

async function nftMintingMachineDemo() {
  // Connect wallet
  const wallet = await MeshCardanoBrowserWallet.enable("eternl");

  // Initialize provider
  const provider = new BlockfrostProvider("<Your-API-Key>");

  // Step 1: Setup Oracle (ONE TIME ONLY)
  const setupMeshTxBuilder = new MeshTxBuilder({
    fetcher: provider,
    submitter: provider,
    verbose: true,
  });

  const setupContract = new MeshPlutusNFTContract(
    {
      mesh: setupMeshTxBuilder,
      fetcher: provider,
      wallet: wallet,
      networkId: 0,
    },
    {
      collectionName: "MeshCollection",
    }
  );

  // Setup oracle with 10 ADA mint price
  const { tx: setupTx, paramUtxo } = await setupContract.setupOracle(10000000);
  const signedSetupTx = await wallet.signTx(setupTx);
  const setupTxHash = await wallet.submitTx(signedSetupTx);

  console.log("Oracle setup:", setupTxHash);
  console.log("paramUtxo:", JSON.stringify(paramUtxo));

  // Wait for confirmation
  await new Promise((resolve) => setTimeout(resolve, 60000));

  // Step 2: Mint NFTs (can be done multiple times)
  const mintMeshTxBuilder = new MeshTxBuilder({
    fetcher: provider,
    submitter: provider,
    verbose: true,
  });

  const mintContract = new MeshPlutusNFTContract(
    {
      mesh: mintMeshTxBuilder,
      fetcher: provider,
      wallet: wallet,
      networkId: 0,
    },
    {
      collectionName: "MeshCollection",
      paramUtxo: paramUtxo, // Use paramUtxo from setup
    }
  );

  // Get current index
  const oracleData = await mintContract.getOracleData();

  // Define metadata
  const assetMetadata = {
    name: `Mesh Token ${oracleData.nftIndex}`,
    image: "ipfs://QmRzicpReutwCkM6aotuKjErFCUD213DpwPq6ByuzMJaua",
    description: `Edition #${oracleData.nftIndex} of MeshCollection`,
  };

  // Mint
  const mintTx = await mintContract.mintPlutusNFT(assetMetadata);
  const signedMintTx = await wallet.signTx(mintTx);
  const mintTxHash = await wallet.submitTx(signedMintTx);

  console.log(`Minted NFT #${oracleData.nftIndex}:`, mintTxHash);
}

nftMintingMachineDemo().catch(console.error);

Important: Storing paramUtxo

The paramUtxo is critical for your minting machine. Store it securely:

// After setupOracle, save this to your database or config
const paramUtxo = {
  outputIndex: 0,
  txHash: "63dbd563ee9979574401599a42841e0d5b63a691af95df863cbf37d5cb44a558",
};

// For all future mints, initialize the contract with this paramUtxo
const contract = new MeshPlutusNFTContract(
  { /* ... */ },
  {
    collectionName: "MeshCollection",
    paramUtxo: paramUtxo,  // Required for minting
  }
);

Troubleshooting

Common Errors

ErrorCauseSolution
Oracle not foundMissing or wrong paramUtxoVerify paramUtxo matches your setup transaction
Script validation failedOracle state mismatchEnsure you're using the latest oracle state
Invalid collection nameName mismatchCollection name must match oracle setup
Insufficient fundsNot enough ADA for mint price + feesEnsure wallet has enough ADA

Debugging Tips

  1. Save paramUtxo immediately: Store it right after setupOracle() succeeds
  2. Use consistent collection name: Must be identical for setup and mint
  3. Check oracle data first: Call getOracleData() before minting to verify state
  4. One mint at a time: Wait for each mint to confirm before the next

Concurrent Minting

The oracle pattern requires sequential updates. For high-volume minting:

// Wait for confirmation between mints
async function mintSequentially(count: number) {
  for (let i = 0; i < count; i++) {
    const oracleData = await contract.getOracleData();
    const tx = await contract.mintPlutusNFT({
      name: `Token ${oracleData.nftIndex}`,
      // ...metadata
    });
    const signedTx = await wallet.signTx(tx);
    await wallet.submitTx(signedTx);

    // Wait for confirmation
    await new Promise((r) => setTimeout(r, 30000));
  }
}

On this page