Mesh LogoMesh

How to Mint an NFT Collection on Cardano with TypeScript

Mint native NFT assets on Cardano using the Mesh TypeScript blockchain SDK and Bun. Learn CIP-25 metadata, native scripts, and transaction building for dApp development.

Minting an NFT collection on Cardano means creating native assets with CIP-25 compliant metadata—no smart contract required. This guide walks you through the complete Cardano dApp development process using the Mesh TypeScript blockchain SDK: wallet generation, native script creation, and submitting your mint transaction on the Cardano preprod testnet.

What you'll learn:

  • How Cardano native tokens work without smart contracts
  • Building minting transactions with the Mesh Cardano SDK
  • Implementing CIP-25 metadata standards for NFTs

Resources:

How Do You Set Up the Project?

First, install Bun from bun.sh, then initialize your project:

bun init
bun install @meshsdk/core

Next, generate a Cardano wallet. Create scripts/brew.ts to generate a new wallet and display its address:

import { MeshWallet } from "@meshsdk/core";

const words = MeshWallet.brew() as string[];

const mnemonicString = words.join(" ");
console.log("mnemonic:", mnemonicString);

const wallet = new MeshWallet({
  key: { type: "mnemonic", words },
  networkId: 0,
});

console.log("Public Change Address:", await wallet.getChangeAddress());

Run the script:

bun run scripts/brew.ts

Copy the public address and fund it using the Cardano testnet faucet. Then create a Blockfrost preprod project and save both credentials to a .env file:

MNEMONIC=your twelve word mnemonic phrase here
BLOCKFROST_KEY=your_blockfrost_api_key

How Do You Build the Minting Transaction?

Create index.ts and import the Mesh Cardano SDK components:

import {
  MeshTxBuilder,
  MeshWallet,
  BlockfrostProvider
} from "@meshsdk/core"

const provider = new BlockfrostProvider(process.env.BLOCKFROST_KEY!);
const words = process.env.MNEMONIC!.split(" ");

const wallet = new MeshWallet({
  key: { type: "mnemonic", words },
  networkId: 0,
  fetcher: provider,
  submitter: provider,
});

const txBuilder = new MeshTxBuilder({ fetcher: provider });

const address = await wallet.getChangeAddress();
const utxos = await wallet.getUtxos();
const { pubKeyHash } = deserializeAddress(address);

What Is a Native Script for Minting?

A native script defines the minting policy—the rules controlling who can mint tokens and when. In Cardano dApp development, native scripts are simpler than Plutus smart contracts while still providing secure minting controls. This example combines a time lock (minting expires after a certain slot) with a signature requirement:

const nativeScript: NativeScript = {
  type: "all",
  scripts: [
    {
      type: "before",
      slot: "90000000", // Any value beyond the current preprod absolute slot. When you read this, it's possible this slot has passed.
    },
    { type: "sig", keyHash: pubKeyHash },
  ],
};

const forgeScript = ForgeScript.fromNativeScript(nativeScript);
const policyId = resolveScriptHash(forgeScript);

The slot value must be in the future—adjust it based on the current preprod slot number.

How Do You Create CIP-25 NFT Metadata?

CIP-25 is the Cardano standard for NFT metadata. Create a function that generates compliant metadata with image references and attributes:

type AssetMetadata = {
  files: {
    mediaType: string;
    name: string;
    src: string;
  }[];
  image: string;
  mediaType: string;
  name: string;
};

function get721Metadata(
  name: string,
  attributes?: Record<string, unknown>
): AssetMetadata {
  return {
    ...attributes,
    files: [
      {
        mediaType: "image/png",
        name,
        src: "ipfs://QmPS4PBvpGc2z6Dd6JdYqfHrKnURjtRGPTJWdhnAXNA8bQ",
      },
    ],
    image: "ipfs://QmPS4PBvpGc2z6Dd6JdYqfHrKnURjtRGPTJWdhnAXNA8bQ",
    mediaType: "image/png",
    name,
  };
}

How Do You Mint Multiple NFTs in One Transaction?

Loop through your collection to mint multiple tokens in a single transaction. This is more efficient and cost-effective than individual minting transactions:

const metadata: {
  [policyId: string]: {
    [assetName: string]: AssetMetadata;
  };
} = { [policyId]: {} };

for (let i = 1; i < 10; i++) {
  const tokenName = "Asset #" + i;
  const tokenHex = stringToHex(tokenName);
  txBuilder.mint("1", policyId, tokenHex).mintingScript(forgeScript);
  metadata[policyId]![tokenName] = get721Metadata(tokenName, { Attribute: i });
}

const unsignedTx = await txBuilder
  .metadataValue(721, metadata)
  .changeAddress(address)
  .invalidHereafter(90000000)
  .selectUtxosFrom(utxos)
  .complete();

const signedTx = await wallet.signTx(unsignedTx);
const txHash = await wallet.submitTx(signedTx);
console.log("Submitted TX Hash:", txHash);

Run the script with bun run index.ts. After submission, view your transaction on a Cardano explorer like CardanoScan using the returned transaction hash.

What Should You Do Next?

Now that you've minted your first NFT collection using this TypeScript blockchain SDK:

  • Add unique images — Replace the placeholder IPFS hash with your own artwork
  • Expand metadata — Add more attributes following the CIP-25 specification
  • Deploy to mainnet — Switch networkId to 1 and use mainnet providers for production Cardano dApp development

On this page