Back to blog
Hedera 2025-03-01

Building NFTs on Hedera: A Complete Guide Using the Token Services

Hedera Hashgraph is one of the most underrated public ledgers for NFT projects. As a Blockchain Full Stack Developer I have worked with Ethereum, Solana, and Hedera – and for high-throughput, low-cost NFT use cases, Hedera consistently wins.

In this guide I will walk you through creating and minting an NFT collection on Hedera using the Hedera Token Service (HTS) – no EVM smart contract needed.

Why Hedera for NFTs?

FeatureEthereumHedera
Transaction fee$1–$50+~$0.001
Finality time~12 seconds~3 seconds
Energy usageModerateCarbon-negative
Throughput~15 TPS10,000+ TPS
NFT standardERC-721HTS native

Hedera achieves this through the hashgraph consensus algorithm – a DAG-based gossip protocol that gives deterministic finality in seconds.

Setting Up the Hedera SDK

Install the official JavaScript SDK:

npm install @hashgraph/sdk

Create a client connected to testnet:

import { Client, AccountId, PrivateKey } from "@hashgraph/sdk";

const operatorId = AccountId.fromString(process.env.OPERATOR_ID);
const operatorKey = PrivateKey.fromStringECDSA(process.env.OPERATOR_KEY);

const client = Client.forTestnet();
client.setOperator(operatorId, operatorKey);

For mainnet just replace Client.forTestnet() with Client.forMainnet().

Step 1: Create the NFT Token Type

Before you can mint individual NFTs you must create the token type – think of this as deploying your collection contract on Ethereum, except there is no contract.

import {
  TokenCreateTransaction,
  TokenType,
  TokenSupplyType,
} from "@hashgraph/sdk";

const createTx = await new TokenCreateTransaction()
  .setTokenName("MrBns Genesis Collection")
  .setTokenSymbol("MBNS")
  .setTokenType(TokenType.NonFungibleUnique) // NFT
  .setSupplyType(TokenSupplyType.Finite)
  .setMaxSupply(100) // hard cap of 100 NFTs
  .setTreasuryAccountId(operatorId)
  .setSupplyKey(operatorKey) // key allowed to mint/burn
  .setAdminKey(operatorKey) // key allowed to update the token
  .execute(client);

const receipt = await createTx.getReceipt(client);
const tokenId = receipt.tokenId;

console.log(`Collection created: ${tokenId}`); // e.g. 0.0.1234567

Step 2: Mint Individual NFTs

Each NFT on Hedera carries metadata – a byte array typically pointing to an IPFS JSON file following the HIP-412 metadata standard.

import { TokenMintTransaction } from "@hashgraph/sdk";

// IPFS CID of your metadata JSON
const metadata = Buffer.from("ipfs://QmYourCID/1.json");

const mintTx = await new TokenMintTransaction()
  .setTokenId(tokenId)
  .addMetadata(metadata) // one addMetadata per NFT
  .execute(client);

const mintReceipt = await mintTx.getReceipt(client);
const serial = mintReceipt.serials[0];

console.log(`Minted NFT serial #${serial}`);

Batch minting (up to 10 per transaction):

const batchMint = new TokenMintTransaction().setTokenId(tokenId);

for (let i = 1; i <= 10; i++) {
  batchMint.addMetadata(Buffer.from(`ipfs://QmYourCID/${i}.json`));
}

await batchMint.execute(client);

Step 3: Associate and Transfer an NFT

Before an account can receive an HTS token it must associate with it (a security feature that prevents spam airdrops):

import {
  TokenAssociateTransaction,
  TransferTransaction,
  NftId,
} from "@hashgraph/sdk";

const recipientId = AccountId.fromString("0.0.9999999");
const recipientKey = PrivateKey.fromStringECDSA(process.env.RECIPIENT_KEY);

// Associate recipient with the token
const assocTx = await new TokenAssociateTransaction()
  .setAccountId(recipientId)
  .setTokenIds([tokenId])
  .freezeWith(client)
  .sign(recipientKey);

await assocTx.execute(client);

// Transfer NFT serial #1 to recipient
const transferTx = await new TransferTransaction()
  .addNftTransfer(new NftId(tokenId, serial), operatorId, recipientId)
  .execute(client);

const transferReceipt = await transferTx.getReceipt(client);
console.log(`Transfer status: ${transferReceipt.status}`);

Step 4: Query NFT Info

You can read on-chain metadata and ownership without any indexer:

import { TokenNftInfoQuery } from "@hashgraph/sdk";

const nftInfo = await new TokenNftInfoQuery()
  .setNftId(new NftId(tokenId, serial))
  .execute(client);

console.log({
  accountId: nftInfo[0].accountId.toString(),
  metadata: Buffer.from(nftInfo[0].metadata).toString(),
});

Metadata Standard (HIP-412)

Follow HIP-412 for marketplace compatibility. Your IPFS JSON should look like:

{
  "name": "MrBns Genesis #1",
  "description": "A genesis NFT from MrBns – Mr Binary Sniper",
  "image": "ipfs://QmImageCID/1.png",
  "type": "image/png",
  "properties": {
    "rarity": "legendary",
    "creator": "MrBns"
  }
}

Summary

Hedera’s Token Service gives you a production-ready NFT system with:

  • Sub-cent transaction fees
  • 3-second finality
  • No smart contract deployment risk
  • Carbon-negative footprint
  • Built-in royalty enforcement (via setRoyaltyFee)

As MrBns I use HTS for any project where cost, speed, and reliability matter more than EVM compatibility. In the next article I will cover the Hedera Token Service for fungible tokens – stablecoins, reward tokens, and governance tokens.

Hedera NFT HTS blockchain MrBns Mr Binary Sniper Blockchain Full Stack Developer