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/coreSetup Overview
The NFT Minting Machine requires a three-step setup:
| Step | Action | Purpose |
|---|---|---|
| 1 | Setup Oracle | Create the one-time minting policy and oracle token |
| 2 | Save paramUtxo | Store the oracle reference for future mints |
| 3 | Mint NFTs | Use the oracle to mint sequentially numbered NFTs |
Contract Logic
The contract uses an oracle pattern to track minting state:
| Component | Description |
|---|---|
| Oracle Token | A one-time minted NFT that holds the collection state |
| Oracle Datum | Stores the current NFT index and collection info |
| Minting Policy | Ensures each NFT is numbered correctly |
How It Works
- Setup: Mint an oracle token that stores
nftIndex = 0 - First Mint: Oracle shows index 0, mint NFT #0, update oracle to index 1
- Second Mint: Oracle shows index 1, mint NFT #1, update oracle to index 2
- 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
| Parameter | Type | Description |
|---|---|---|
priceLovelace | number | Price 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
| Parameter | Type | Description |
|---|---|---|
assetMetadata | object | CIP-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
| Error | Cause | Solution |
|---|---|---|
Oracle not found | Missing or wrong paramUtxo | Verify paramUtxo matches your setup transaction |
Script validation failed | Oracle state mismatch | Ensure you're using the latest oracle state |
Invalid collection name | Name mismatch | Collection name must match oracle setup |
Insufficient funds | Not enough ADA for mint price + fees | Ensure wallet has enough ADA |
Debugging Tips
- Save paramUtxo immediately: Store it right after
setupOracle()succeeds - Use consistent collection name: Must be identical for setup and mint
- Check oracle data first: Call
getOracleData()before minting to verify state - 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));
}
}