Create and Transfer Your First NFT

Summary

Using the Hedera Token Service, you can create non-fungible tokens (NFTs). NFTs are uniquely identifiable. On the Hedera network, the token ID represents a collection of NFTs of the same class, and the serial number of each token uniquely identifies each NFT in the class.


Prerequisites

We recommend you complete the following introduction to get a basic understanding of Hedera transactions. This example does not build upon the previous examples.


1. Create a Non-Fungible Token (NFT)

Use TokenCreateTransaction() to configure and set the token properties. At a minimum, this constructor requires setting a name, symbol, and treasury account ID. All other fields are optional, so if they’re not specified then default values are used. For instance, not specifying an admin key, makes a token immutable (can’t change or add properties); not specifying a supply key, makes a token supply fixed (can’t mint new or burn existing tokens); not specifying a token type, makes a token fungible.

After submitting the transaction to the Hedera network, you can obtain the new token ID by requesting the receipt. This token ID represents an NFT class.

//Create the NFT
TokenCreateTransaction nftCreate = new TokenCreateTransaction()
        .setTokenName("diploma")
        .setTokenSymbol("GRAD")
        .setTokenType(TokenType.NON_FUNGIBLE_UNIQUE)
        .setDecimals(0)
        .setInitialSupply(0)
        .setTreasuryAccountId(treasuryId)
        .setSupplyType(TokenSupplyType.FINITE)
        .setMaxSupply(250)
        .setSupplyKey(supplyKey)
        .freezeWith(client);


//Sign the transaction with the treasury key
TokenCreateTransaction nftCreateTxSign = nftCreate.sign(treasuryKey);

//Submit the transaction to a Hedera network
TransactionResponse nftCreateSubmit = nftCreateTxSign.execute(client);

//Get the transaction receipt
TransactionReceipt nftCreateRx = nftCreateSubmit.getReceipt(client);

//Get the token ID
TokenId tokenId = nftCreateRx.tokenId;

//Log the token ID
System.out.println("Created NFT with token ID " +tokenId);

2. Mint a New NFT

When creating an NFT, the decimals and initial supply must be set to zero. After the token is created, you mint each NFT using the token mint operation. Specifying a supply key during token creation is a requirement to be able to mint and burn tokens. The supply key is required to sign mint and burn transactions.

Both the NFT image and metadata live in the InterPlanetary File System (IPFS), which provides decentralized storage. The file diploma_metadata.json contains the metadata for the NFT. A content identifier (CID) pointing to the metadata file is used during minting of the new NFT. Notice that the metadata file contains a URI pointing to the NFT image.

// Max transaction fee as a constant
final int MAX_TRANSACTION_FEE = 20;

// IPFS content identifiers for which we will create a NFT
String[] CID = {
        "ipfs://bafyreiao6ajgsfji6qsgbqwdtjdu5gmul7tv2v3pd6kjgcw5o65b2ogst4/metadata.json",
        "ipfs://bafyreic463uarchq4mlufp7pvfkfut7zeqsqmn3b2x3jjxwcjqx6b5pk7q/metadata.json",
        "ipfs://bafyreihhja55q6h2rijscl3gra7a3ntiroyglz45z5wlyxdzs6kjh2dinu/metadata.json",
        "ipfs://bafyreidb23oehkttjbff3gdi4vz7mjijcxjyxadwg32pngod4huozcwphu/metadata.json",
        "ipfs://bafyreie7ftl6erd5etz5gscfwfiwjmht3b52cevdrf7hjwxx5ddns7zneu/metadata.json"
};

//Mint a new NFT
TokenMintTransaction mintTx = new TokenMintTransaction()
        .setTokenId(tokenId)
        .setMaxTransactionFee(new Hbar(MAX_TRANSACTION_FEE))
        .freezeWith(client);

for (String cid : CID) {
    mintTx.addMetadata(cid.getBytes());
}

//Sign transaction with the supply key
TokenMintTransaction mintTxSign = mintTx.sign(supplyKey);

//Submit the transaction to a Hedera network
TransactionResponse mintTxSubmit = mintTxSign.execute(client);

//Get the transaction receipt
TransactionReceipt mintRx = mintTxSubmit.getReceipt(client);

//Log the serial number
System.out.println("Created NFT " + tokenId + " with serial: " + mintRx.serials);
diploma_metadata.json
{
	"name": "Diploma",
	"description": "Certificate of Graduation",
	"image": "https://ipfs.io/ipfs/QmdTNJDYePd4EUUzYPuhc1GsDELjab6ypgvDQAp4visoM9?filename=diploma.jpg",
	"properties": {
		"university": "H State University",
		"college": "Engineering and Applied Sciences",
		"level": "Masters",
		"field": "Mechanical Engineering",
		"honors": "yes",
		"honorsType": "Summa Cum Laude",
		"gpa": "3.84",
		"student": "Alice",
		"date": "2021-03-18"
	}
}

🚨 Throttle cap warning

Batch minting of 10+ NFTs can run into throughput issues and potentially hit the transaction limit (throttle cap), causing the SDK to throw BUSY exceptions. Adding an application-level retry loop can help manage these exceptions and increase the robustness of the batch-minting process.

The following are examples of retry loops:

private static final int MAX_RETRIES = 5;

private static TransactionReceipt executeTransaction
(Transaction<?> transaction, PrivateKey key) 
throws Exception {
    int retries = 0;a

    while (retries < MAX_RETRIES) {
        try {
            TransactionResponse txResponse = transaction.sign(key).execute(client);
            TransactionReceipt txReceipt = txResponse.getReceipt(client);

            return txReceipt;
        } catch (PrecheckStatusException e) {
            if (e.status == Status.BUSY) {
                retries++;
                System.out.println("Retry attempt: " + retries);
            } else {
                throw e;
            }
        }
    }
    
    throw new Exception("Transaction failed after " + MAX_RETRIES + " attempts");
}

3. Associate User Accounts with the NFT

Before an account that is not the treasury for a token can receive or send this specific token ID, the account must become “associated” with the token. To associate a token to an account the account owner must sign the associate transaction.

If you have an account with the automatic token association property set you do not need to associate the token before transferring it the receiving account.

//Create the associate transaction and sign with Alice's key 
TokenAssociateTransaction associateAliceTx = new TokenAssociateTransaction()
        .setAccountId(aliceAccountId)
        .setTokenIds(Collections.singletonList(tokenId))
	.freezeWith(client)
        .sign(aliceKey);

//Submit the transaction to a Hedera network
TransactionResponse associateAliceTxSubmit = associateAliceTx.execute(client);

//Get the transaction receipt
TransactionReceipt associateAliceRx = associateAliceTxSubmit.getReceipt(client);

//Confirm the transaction was successful
System.out.println("NFT association with Alice's account: " +associateAliceRx.status);

4. Transfer the NFT

Now, transfer the NFT and check the account balances before and after the send! After the transfer you should expect to the NFT to be removed from the treasury account and available in Alice's account. The treasury account key has to sign the transfer transaction to authorize the transfer to Alice's account.

// Check the balance before the transfer for the treasury account
AccountBalance balanceCheckTreasury = new AccountBalanceQuery().setAccountId(treasuryId).execute(client);
System.out.println("Treasury balance: " +balanceCheckTreasury.tokens + "NFTs of ID " +tokenId);

// Check the balance before the transfer for Alice's account
AccountBalance balanceCheckAlice = new AccountBalanceQuery().setAccountId(aliceAccountId).execute(client);
System.out.println("Alice's balance: " +balanceCheckAlice.tokens + "NFTs of ID " +tokenId);

// Transfer the NFT from treasury to Alice
// Sign with the treasury key to authorize the transfer
TransferTransaction tokenTransferTx = new TransferTransaction()
        .addNftTransfer( new NftId(tokenId, 1), treasuryId, aliceAccountId)
        .freezeWith(client)
        .sign(treasuryKey);

TransactionResponse tokenTransferSubmit = tokenTransferTx.execute(client);
TransactionReceipt tokenTransferRx = tokenTransferSubmit.getReceipt(client);

System.out.println("NFT transfer from Treasury to Alice: " +tokenTransferRx.status);

// Check the balance of the treasury account after the transfer
AccountBalance balanceCheckTreasury2 = new AccountBalanceQuery().setAccountId(treasuryId).execute(client);
System.out.println("Treasury balance: " +balanceCheckTreasury2.tokens + "NFTs of ID " + tokenId);

// Check the balance of Alice's account after the transfer
AccountBalance balanceCheckAlice2 = new AccountBalanceQuery().setAccountId(aliceAccountId).execute(client);
System.out.println("Alice's balance: " +balanceCheckAlice2.tokens +  "NFTs of ID " +tokenId);

Code Check ✅

Java
import com.hedera.hashgraph.sdk.*;
import io.github.cdimascio.dotenv.Dotenv;

import java.util.Collections;
import java.util.List;
import java.util.Objects;
import java.util.concurrent.TimeoutException;

public class CreateNFTTutorial {

        public static void main(String[] args)
                        throws TimeoutException, PrecheckStatusException, ReceiptStatusException {

                // Grab your Hedera testnet account ID and private key
                AccountId myAccountId = AccountId
                                .fromString(Objects.requireNonNull(Dotenv.load().get("MY_ACCOUNT_ID")));
                PrivateKey myPrivateKey = PrivateKey
                                .fromString(Objects.requireNonNull(Dotenv.load().get("MY_PRIVATE_KEY")));

                // Create your Hedera testnet client
                Client client = Client.forTestnet();
                client.setOperator(myAccountId, myPrivateKey);

                // Treasury Key
                PrivateKey treasuryKey = PrivateKey.generateED25519();
                PublicKey treasuryPublicKey = treasuryKey.getPublicKey();

                // Create treasury account
                TransactionResponse treasuryAccount = new AccountCreateTransaction()
                                .setKey(treasuryPublicKey)
                                .setInitialBalance(new Hbar(10))
                                .execute(client);

                AccountId treasuryId = treasuryAccount.getReceipt(client).accountId;

                // Alice Key
                PrivateKey aliceKey = PrivateKey.generateED25519();
                PublicKey alicePublicKey = aliceKey.getPublicKey();

                // Create Alice's account
                TransactionResponse aliceAccount = new AccountCreateTransaction()
                                .setKey(alicePublicKey)
                                .setInitialBalance(new Hbar(10))
                                .execute(client);

                AccountId aliceAccountId = aliceAccount.getReceipt(client).accountId;

                // Supply Key
                PrivateKey supplyKey = PrivateKey.generateED25519();
                PublicKey supplyPublicKey = supplyKey.getPublicKey();

                // Create the NFT
                TokenCreateTransaction nftCreate = new TokenCreateTransaction()
                                .setTokenName("diploma")
                                .setTokenSymbol("GRAD")
                                .setTokenType(TokenType.NON_FUNGIBLE_UNIQUE)
                                .setDecimals(0)
                                .setInitialSupply(0)
                                .setTreasuryAccountId(treasuryId)
                                .setSupplyType(TokenSupplyType.FINITE)
                                .setMaxSupply(250)
                                .setSupplyKey(supplyKey)
                                .freezeWith(client);

                // Sign the transaction with the treasury key
                TokenCreateTransaction nftCreateTxSign = nftCreate.sign(treasuryKey);

                // Submit the transaction to a Hedera network
                TransactionResponse nftCreateSubmit = nftCreateTxSign.execute(client);

                // Get the transaction receipt
                TransactionReceipt nftCreateRx = nftCreateSubmit.getReceipt(client);

                // Get the token ID
                TokenId tokenId = nftCreateRx.tokenId;

                // Log the token ID
                System.out.println("Created NFT with token ID " + tokenId);

                // Max transaction fee as a constant
                final int MAX_TRANSACTION_FEE = 20;

                // IPFS content identifiers for which we will create a NFT
                String[] CID = {
                                "ipfs://bafyreiao6ajgsfji6qsgbqwdtjdu5gmul7tv2v3pd6kjgcw5o65b2ogst4/metadata.json",
                                "ipfs://bafyreic463uarchq4mlufp7pvfkfut7zeqsqmn3b2x3jjxwcjqx6b5pk7q/metadata.json",
                                "ipfs://bafyreihhja55q6h2rijscl3gra7a3ntiroyglz45z5wlyxdzs6kjh2dinu/metadata.json",
                                "ipfs://bafyreidb23oehkttjbff3gdi4vz7mjijcxjyxadwg32pngod4huozcwphu/metadata.json",
                                "ipfs://bafyreie7ftl6erd5etz5gscfwfiwjmht3b52cevdrf7hjwxx5ddns7zneu/metadata.json"
                };

                // Mint a new NFT
                TokenMintTransaction mintTx = new TokenMintTransaction()
                                .setTokenId(tokenId)
                                .setMaxTransactionFee(new Hbar(MAX_TRANSACTION_FEE))
                                .freezeWith(client);

                for (String cid : CID) {
                        mintTx.addMetadata(cid.getBytes());
                }

                // Sign transaction with the supply key
                TokenMintTransaction mintTxSign = mintTx.sign(supplyKey);

                // Submit the transaction to a Hedera network
                TransactionResponse mintTxSubmit = mintTxSign.execute(client);

                // Get the transaction receipt
                TransactionReceipt mintRx = mintTxSubmit.getReceipt(client);

                // Log the serial number
                System.out.println("Created NFT " + tokenId + " with serial: " + mintRx.serials);

                // Create the associate transaction and sign with Alice's key
                TokenAssociateTransaction associateAliceTx = new TokenAssociateTransaction()
                                .setAccountId(aliceAccountId)
                                .setTokenIds(Collections.singletonList(tokenId))
                                .freezeWith(client)
                                .sign(aliceKey);

                // Submit the transaction to a Hedera network
                TransactionResponse associateAliceTxSubmit = associateAliceTx.execute(client);

                // Get the transaction receipt
                TransactionReceipt associateAliceRx = associateAliceTxSubmit.getReceipt(client);

                // Confirm the transaction was successful
                System.out.println("NFT association with Alice's account: " + associateAliceRx.status);

                // Check the balance before the NFT transfer for the treasury account
                AccountBalance balanceCheckTreasury = new AccountBalanceQuery().setAccountId(treasuryId)
                                .execute(client);
                System.out.println("Treasury balance: " + balanceCheckTreasury.tokens + "NFTs of ID " + tokenId);

                // Check the balance before the NFT transfer for Alice's account
                AccountBalance balanceCheckAlice = new AccountBalanceQuery().setAccountId(aliceAccountId)
                                .execute(client);
                System.out.println("Alice's balance: " + balanceCheckAlice.tokens + "NFTs of ID " + tokenId);

                // Transfer NFT from treasury to Alice
                // Sign with the treasury key to authorize the transfer
                TransferTransaction tokenTransferTx = new TransferTransaction()
                                .addNftTransfer(new NftId(tokenId, 1), treasuryId, aliceAccountId)
                                .freezeWith(client)
                                .sign(treasuryKey);

                TransactionResponse tokenTransferSubmit = tokenTransferTx.execute(client);
                TransactionReceipt tokenTransferRx = tokenTransferSubmit.getReceipt(client);

                System.out.println("NFT transfer from Treasury to Alice: " + tokenTransferRx.status);

                // Check the balance for the treasury account after the transfer
                AccountBalance balanceCheckTreasury2 = new AccountBalanceQuery().setAccountId(treasuryId)
                                .execute(client);
                System.out.println("Treasury balance: " + balanceCheckTreasury2.tokens + "NFTs of ID " + tokenId);

                // Check the balance for Alice's account after the transfer
                AccountBalance balanceCheckAlice2 = new AccountBalanceQuery().setAccountId(aliceAccountId)
                                .execute(client);
                System.out.println("Alice's balance: " + balanceCheckAlice2.tokens + "NFTs of ID " + tokenId);

        }
}
JavaScript
console.clear();
require("dotenv").config();
const {
  AccountId,
  PrivateKey,
  Client,
  Hbar,
  TokenCreateTransaction,
  TokenType,
  TokenSupplyType,
  TokenMintTransaction,
  TransferTransaction,
  AccountBalanceQuery,
  TokenAssociateTransaction,
} = require("@hashgraph/sdk");

// Configure accounts and client, and generate needed keys
const operatorId = AccountId.fromString(process.env.OPERATOR_ID);
const operatorKey = PrivateKey.fromString(process.env.OPERATOR_PVKEY);
const treasuryId = AccountId.fromString(process.env.TREASURY_ID);
const treasuryKey = PrivateKey.fromString(process.env.TREASURY_PVKEY);
const aliceId = AccountId.fromString(process.env.ALICE_ID);
const aliceKey = PrivateKey.fromString(process.env.ALICE_PVKEY);

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

const supplyKey = PrivateKey.generate();

async function createFirstNft() {
  //Create the NFT
  const nftCreate = await new TokenCreateTransaction()
    .setTokenName("diploma")
    .setTokenSymbol("GRAD")
    .setTokenType(TokenType.NonFungibleUnique)
    .setDecimals(0)
    .setInitialSupply(0)
    .setTreasuryAccountId(treasuryId)
    .setSupplyType(TokenSupplyType.Finite)
    .setMaxSupply(250)