Evolution SDK
Smart Contracts

Tutorial: Mint an NFT

Mint a Cardano NFT with CIP-25 metadata — from minting policy to on-chain token

Tutorial: Mint an NFT

Mint a unique NFT on Cardano with CIP-25 metadata — a name, image, and description stored on-chain. This tutorial uses a native script minting policy (no Plutus required) and attaches standard NFT metadata.

What You'll Build

  • A native script minting policy that only you can mint from
  • An NFT (quantity 1) with CIP-25 metadata (name, image, description)
  • A transaction that mints the token, attaches metadata, and sends it to a recipient

Prerequisites

Step 1: Create a Minting Policy

A simple native script policy — only your key can mint:

import { NativeScripts, Bytes } from "@evolution-sdk/evolution"

// Your key hash (28 bytes) — from your wallet
const myKeyHash = Bytes.fromHex(
  "abc123def456abc123def456abc123def456abc123def456abc123de"
)

// Minting policy: only this key can authorize minting
const mintingPolicy = NativeScripts.makeScriptPubKey(myKeyHash)
const nativeScript = new NativeScripts.NativeScript({ script: mintingPolicy })

// The policy ID = blake2b-224 hash of the script CBOR
// After minting, you can find it in the transaction output or compute it offline
// For this tutorial, we'll use the known policy ID in subsequent steps

For a one-time mint (true NFT uniqueness), add a time-lock to the policy. After the deadline passes, nobody can mint more tokens under this policy. See Native Scripts for time-lock examples.

Step 2: Structure CIP-25 Metadata

CIP-25 defines the standard metadata format for Cardano NFTs. It uses transaction metadata label 721:

import { TransactionMetadatum } from "@evolution-sdk/evolution"

// Your policy ID (28 bytes = 56 hex chars)
const policyId = "abc123def456abc123def456abc123def456abc123def456abc123de"

// Asset name in hex — e.g., "MyNFT001" → hex
const assetNameHex = "4d794e4654303031" // "MyNFT001" in hex

// CIP-25 metadata structure:
// { 721: { <policyId>: { <assetName>: { name, image, ... } } } }
const nftMetadata = new Map<TransactionMetadatum.TransactionMetadatum, TransactionMetadatum.TransactionMetadatum>([
  [policyId, new Map<TransactionMetadatum.TransactionMetadatum, TransactionMetadatum.TransactionMetadatum>([
    [assetNameHex, new Map<TransactionMetadatum.TransactionMetadatum, TransactionMetadatum.TransactionMetadatum>([
      ["name", "My First NFT"],
      ["image", "ipfs://QmYourImageHashHere"],
      ["mediaType", "image/png"],
      ["description", "My first NFT minted with Evolution SDK"],
    ])]
  ])]
])

CIP-25 Required Fields

FieldTypeDescription
namestringDisplay name of the NFT
imagestringURI to the image (typically ipfs://...)

CIP-25 Optional Fields

FieldTypeDescription
mediaTypestringMIME type of the image (e.g., image/png)
descriptionstringHuman-readable description
filesarrayAdditional files (for multi-asset NFTs)

Step 3: Mint, Attach Metadata, and Send

Put it all together — mint the NFT, attach CIP-25 metadata, and send to a recipient:

import {
  Address, Assets, NativeScripts, Bytes, TransactionMetadatum,
  preprod, Client
} from "@evolution-sdk/evolution"

const client = Client.make(preprod)
  .withBlockfrost({
    baseUrl: "https://cardano-preprod.blockfrost.io/api/v0",
    projectId: process.env.BLOCKFROST_API_KEY!,
  })
  .withSeed({ mnemonic: process.env.WALLET_MNEMONIC!, accountIndex: 0 })

// --- Policy ---
const myKeyHash = Bytes.fromHex("abc123def456abc123def456abc123def456abc123def456abc123de")
const mintingPolicy = NativeScripts.makeScriptPubKey(myKeyHash)
const nativeScript = new NativeScripts.NativeScript({ script: mintingPolicy })

// --- NFT identity ---
const policyId = "abc123def456abc123def456abc123def456abc123def456abc123de" // script hash
const assetName = "4d794e4654303031" // "MyNFT001" in hex

// --- Mint assets (quantity 1 = NFT) ---
let mintAssets = Assets.fromLovelace(0n)
mintAssets = Assets.addByHex(mintAssets, policyId, assetName, 1n)

// --- Send assets (NFT + min ADA for UTxO) ---
let sendAssets = Assets.fromLovelace(2_000_000n)
sendAssets = Assets.addByHex(sendAssets, policyId, assetName, 1n)

// --- CIP-25 metadata ---
const nftMetadata = new Map<TransactionMetadatum.TransactionMetadatum, TransactionMetadatum.TransactionMetadatum>([
  [policyId, new Map<TransactionMetadatum.TransactionMetadatum, TransactionMetadatum.TransactionMetadatum>([
    [assetName, new Map<TransactionMetadatum.TransactionMetadatum, TransactionMetadatum.TransactionMetadatum>([
      ["name", "My First NFT"],
      ["image", "ipfs://QmYourImageHashHere"],
      ["mediaType", "image/png"],
      ["description", "Minted with Evolution SDK"],
    ])]
  ])]
])

const recipient = Address.fromBech32(
  "addr_test1vrm9x2dgvdau8vckj4duc89m638t8djmluqw5pdrFollw8qd9k63"
)

// --- Build transaction ---
const tx = await client
  .newTx()
  .mintAssets({ assets: mintAssets })           // mint 1 NFT
  .attachScript({ script: nativeScript })        // attach minting policy
  .attachMetadata({ label: 721n, metadata: nftMetadata }) // CIP-25 metadata
  .payToAddress({ address: recipient, assets: sendAssets }) // send NFT to recipient
  .build()

const signed = await tx.sign()
const txHash = await signed.submit()
// txHash → your NFT is now on-chain!

How It Works

  1. mintAssets — creates 1 token under your policy ID (quantity 1 = non-fungible)
  2. attachScript — includes the native script so the ledger can verify your minting authority
  3. attachMetadata — adds CIP-25 metadata under label 721 (the NFT metadata standard)
  4. payToAddress — sends the minted NFT + min ADA to the recipient

The builder handles fee calculation, coin selection, and change automatically.

Common Pitfalls

Metadata label must be 721n (bigint). Using 721 (number) will cause a type error. CIP-25 requires label 721.

ProblemCauseFix
NFT not showing in walletWrong metadata structureEnsure policy ID and asset name in metadata match the minted token exactly
"Minting not allowed"Wrong key signedEnsure the signing wallet's key hash matches the minting policy
Missing imageInvalid IPFS URIUse full ipfs://Qm... format, pin the file first
Type error on labelUsing number instead of bigintUse 721n not 721
Min UTxO too lowNot enough ADA with the NFTInclude at least 2 ADA with the NFT output

Next Steps