Mint NFTs on Cardano with Node.js
Build a server-side NFT minting application using Mesh SDK. Mint tokens and distribute them to multiple recipients.
Overview
Server-side minting lets you create Cardano NFTs without a browser. This is ideal for automated token distribution, airdrops, and backend services.
What you will build
- A Node.js application that mints multiple NFTs
- Automated distribution to multiple recipient addresses
- CIP-25 compliant metadata handling
Prerequisites
- Node.js 18+ installed
- A Blockfrost API key (get one free)
- A funded Cardano wallet (mnemonic or CLI keys)
Time to complete
30 minutes
Quick Start
Clone the example repository:
git clone https://github.com/MeshJS/examples
cd examples/nodejs-minting
npm installAdd your credentials to the config and run npm start.
Step-by-Step Guide
Step 1: Set up the project
Create a new Node.js project:
mkdir nodejs-minting
cd nodejs-minting
npm init -y
npm install typescript @meshsdk/core --save
npm install --save-dev typescript
npx tsc --initWhat to expect: A TypeScript project with Mesh SDK installed.
Step 2: Configure TypeScript
Update tsconfig.json:
{
"compilerOptions": {
"target": "ESNext",
"module": "ESNext",
"moduleResolution": "Node",
"outDir": "dist",
"strict": true,
"esModuleInterop": true
}
}Update package.json:
{
"type": "module",
"scripts": {
"start": "tsc && node ./dist/main.js"
}
}What to expect: TypeScript configured for ES modules.
Step 3: Define NFT metadata
Create src/metadata.ts:
export interface NFTMetadata {
name: string;
image: string;
mediaType: string;
description: string;
artist?: string;
attributes?: Record<string, unknown>;
}
export const metadata: Record<string, NFTMetadata> = {
MeshToken01: {
name: "Mesh Token 1",
image: "ipfs://QmRzicpReutwCkM6aotuKjErFCUD213DpwPq6ByuzMJaua",
mediaType: "image/jpg",
description: "A purple coin from the Mesh collection.",
artist: "Mesh (https://meshjs.dev/)",
},
MeshToken02: {
name: "Mesh Token 2",
image: "ipfs://QmRzicpReutwCkM6aotuKjErFCUD213DpwPq6ByuzMJaua",
mediaType: "image/jpg",
description: "A gold coin from the Mesh collection.",
artist: "Mesh (https://meshjs.dev/)",
},
MeshToken03: {
name: "Mesh Token 3",
image: "ipfs://QmRzicpReutwCkM6aotuKjErFCUD213DpwPq6ByuzMJaua",
mediaType: "image/jpg",
description: "A coin with M logo from the Mesh collection.",
artist: "Mesh (https://meshjs.dev/)",
},
};What to expect: CIP-25 compliant metadata for three tokens.
Step 4: Specify recipients
Create src/recipients.ts:
// Map of recipient address to token name
export const recipients: Record<string, string> = {
"addr_test1vpvx0sacufuypa2k4sngk7q40zc5c4npl337uusdh64kv0c7e4cxr": "MeshToken01",
"addr_test1qqlcxawu4gxarenqvdqyw0tqyjy69mrgsmfqhm6h65jwm4vvldqg2n2p8y4kyjm8sqfyg0tpq9042atz0fr8c3grjmyscxry4r": "MeshToken02",
"addr_test1qq5tay78z9l77vkxvrvtrv70nvjdk0fyvxmqzs57jg0vq6wk3w9pfppagj5rc4wsmlfyvc8xs7ytkumazu9xq49z94pqzl95zt": "MeshToken03",
};What to expect: Each recipient receives one specific token.
Step 5: Create the minting script
Create src/main.ts:
import {
MeshTxBuilder,
ForgeScript,
BlockfrostProvider,
resolveScriptHash,
stringToHex,
} from "@meshsdk/core";
import { MeshCardanoHeadlessWallet, AddressType } from "@meshsdk/wallet";
import type { AssetMetadata } from "@meshsdk/core";
import { metadata } from "./metadata.js";
import { recipients } from "./recipients.js";
// Configuration - replace with your values
const BLOCKFROST_KEY = "your_blockfrost_preprod_key";
const NETWORK_ID = 0; // 0 = testnet, 1 = mainnet
// Wallet mnemonic (24 words)
const MNEMONIC = [
"your", "twenty", "four", "word", "mnemonic",
"phrase", "goes", "here", "replace", "with",
"actual", "words", "from", "your", "wallet",
"that", "has", "been", "funded", "with",
"test", "ada", "from", "faucet",
];
async function main() {
// Initialize provider
const provider = new BlockfrostProvider(BLOCKFROST_KEY);
// Initialize wallet
const wallet = await MeshCardanoHeadlessWallet.fromMnemonic({
networkId: NETWORK_ID,
walletAddressType: AddressType.Base,
fetcher: provider,
submitter: provider,
mnemonic: MNEMONIC,
});
const walletAddress = await wallet.getChangeAddressBech32();
console.log("Wallet address:", walletAddress);
// Create forging script from wallet address
const forgingScript = ForgeScript.withOneSignature(walletAddress);
const policyId = resolveScriptHash(forgingScript);
console.log("Policy ID:", policyId);
// Get UTxOs
const utxos = await wallet.getUtxosMesh();
if (utxos.length === 0) {
throw new Error("No UTxOs available. Fund the wallet first.");
}
console.log("Available UTxOs:", utxos.length);
// Build the transaction
const txBuilder = new MeshTxBuilder({
fetcher: provider,
verbose: true,
});
// Prepare transaction metadata
const txMetadata: Record<string, Record<string, AssetMetadata>> = {
[policyId]: {},
};
// Add mint and output for each recipient
for (const [recipientAddress, assetName] of Object.entries(recipients)) {
const assetMetadata = metadata[assetName];
if (!assetMetadata) {
console.warn(`No metadata found for ${assetName}, skipping`);
continue;
}
const tokenNameHex = stringToHex(assetName);
const assetUnit = policyId + tokenNameHex;
console.log(`Minting ${assetName} for ${recipientAddress.slice(0, 20)}...`);
// Add metadata for this token
txMetadata[policyId][assetName] = assetMetadata;
// Add mint instruction and output
txBuilder
.mint("1", policyId, tokenNameHex)
.mintingScript(forgingScript)
.txOut(recipientAddress, [{ unit: assetUnit, quantity: "1" }]);
}
// Complete the transaction
const unsignedTx = await txBuilder
.metadataValue(721, txMetadata)
.changeAddress(walletAddress)
.selectUtxosFrom(utxos)
.complete();
// Sign and submit
const signedTx = await wallet.signTx(unsignedTx, false);
const txHash = await wallet.submitTx(signedTx);
console.log("");
console.log("Transaction submitted successfully!");
console.log("Transaction hash:", txHash);
console.log(`View on explorer: https://preprod.cardanoscan.io/transaction/${txHash}`);
}
main().catch((error) => {
console.error("Error:", error.message);
process.exit(1);
});What to expect: A complete minting script that distributes tokens to multiple recipients.
Step 6: Run the script
Execute the minting:
npm startWhat to expect: Console output showing each mint operation and the final transaction hash.
Complete Example
Here is the complete project structure:
nodejs-minting/
package.json
tsconfig.json
src/
main.ts
metadata.ts
recipients.tspackage.json:
{
"name": "nodejs-minting",
"type": "module",
"scripts": {
"start": "tsc && node ./dist/main.js"
},
"dependencies": {
"@meshsdk/core": "^1.0.0"
},
"devDependencies": {
"typescript": "^5.0.0"
}
}Next Steps
- Minting API reference - Advanced minting options
- Multi-signature minting - Coordinate multiple signers
- Headless Wallet options - Different key types
- Mint a collection - Batch minting with Bun
Troubleshooting
No UTxOs available
Cause: The wallet has no funds.
Solution: Fund the wallet using the testnet faucet or transfer ADA from another wallet.
Transaction too large
Cause: Too many mints or outputs in one transaction.
Solution: Split the recipients into multiple batches and submit separate transactions.
Invalid address
Cause: Recipient address is malformed or for wrong network.
Solution: Verify addresses are valid Cardano addresses for the correct network (testnet or mainnet).
Mnemonic validation failed
Cause: The mnemonic phrase is incorrect or incomplete.
Solution: Ensure you have exactly 24 words (or 15/12 for some wallets) with correct spelling.
Policy ID mismatch
Cause: The forging script changed between mints.
Solution: The policy ID is deterministic based on the wallet address. Ensure you use the same wallet for all mints of a collection.
Related Links
Build Your First Cardano dApp with Next.js
Create a Cardano dApp using Next.js and Mesh SDK. Connect wallets, query assets, and build your first blockchain application.
Build Multi-Signature Minting Transactions
Implement multi-sig NFT minting with Mesh SDK. Coordinate signatures between browser and server wallets.