Mesh LogoMesh

Mint an NFT Collection

Course Materials:

  1. Install Bun (https://bun.sh/docs/installation)
  2. Fund Your Preprod Wallet (https://docs.cardano.org/cardano-testnets/tools/faucet)
  3. Create a Blockfrost Preprod Project (https://blockfrost.io/dashboard)
  4. CIP-20 Definition (https://cips.cardano.org/cip/CIP-20)

Learn how to mint native assets on the Cardano blockchain. You will:

  1. Understand tokens on Cardano.
  2. Build simple minting transactions.
  3. Understand community standards for NFTs.

Project Setup

Install Bun (see course material #1).

Initialize a new directory and run:

bun init
bun install @meshsdk/core

Generate and fund a Cardano wallet. Create /scripts/brew.ts and use MeshSDK to generate a wallet and log information.

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 your public address and fund the wallet (course material #2). Obtain a Blockfrost Preprod project (course material #3).

Copy your mnemonic and Blockfrost key into a .env file.

Mint Your Collection

In index.ts, use Mesh building blocks to interact with the blockchain.

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);

Create a minting ruleset using a native script to derive the policy ID.

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);

Write a function to generate on-chain metadata for image 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,
  };
}

Loop to create 9 unique tokens using the forge script. Commit, sign, and submit the transaction.

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);

Read the transaction hash and view the transaction on a chain explorer.