Non-fungible Token

In this tutorial, we'll guide you through the process of working with Hedera Token Service (HTS) for non-fungible tokens (NFTs) using JavaScript scripts. These scripts cover essential operations such as creating an NFT, getting NFT information, minting and transferring NFTs, transferring and burning NFTs.

Prerequisites

Before you start, make sure you have the following:

  • Node.js installed on your machine.

  • A Hedera Hashgraph account for testing purposes.

  • Hedera JavaScript SDK installed in your project.

Step 1: Set Up Environment

  1. Create a new directory for your project and navigate into it.

mkdir HederaNFTTutorial
cd HederaNFTTutorial
  1. Initialise your Node.js project and install the necessary dependencies.

npm init -y
npm install @hashgraph/sdk dotenv
  1. Create a .env file to store your Hedera account details.

MY_ACCOUNT_ID=your_account_id
MY_PRIVATE_KEY=your_private_key
OTHER_ACCOUNT_ID=another_account_id
OTHER_PRIVATE_KEY=another_private_key

Step 2: Write NFT Creation Script

Create a file named createNFT.js and add the script for NFT creation.

const {
    TokenCreateTransaction,
    Client,
    TokenType,
    TokenSupplyType,
    PrivateKey,
    AccountBalanceQuery
} = require("@hashgraph/sdk");
require('dotenv').config({ path: 'Token_Service/.env' });

const myAccountId = process.env.MY_ACCOUNT_ID;
const myPrivateKey = PrivateKey.fromString(process.env.MY_PRIVATE_KEY);

// If we weren't able to grab it, we should throw a new error
if (myAccountId == null ||
    myPrivateKey == null ) {
    throw new Error("Environment variables myAccountId and myPrivateKey must be present");
}

// Create our connection to the Hedera network
// The Hedera JS SDK makes this really easy!
const client = Client.forTestnet();

client.setOperator(myAccountId, myPrivateKey);

async function main() {

    //Create the NFT
    let nftCreate = await new TokenCreateTransaction()
        .setTokenName("polyversity")
        .setTokenSymbol("POL")
        .setTokenType(TokenType.NonFungibleUnique)
        .setDecimals(0)
        .setInitialSupply(0)
        .setTreasuryAccountId(myAccountId)
        .setSupplyType(TokenSupplyType.Finite)
        .setMaxSupply(5)
        .setSupplyKey(myPrivateKey)
        .freezeWith(client);

    //Sign the transaction with the treasury key
    let nftCreateTxSign = await nftCreate.sign(myPrivateKey);

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

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

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

    //Log the token ID
    console.log(`- Created NFT with Token ID: ${tokenId} \n`);

    const balanceCheckTx = await new AccountBalanceQuery().setAccountId(myAccountId).execute(client);

    console.log(`- User balance: ${balanceCheckTx.tokens._map.get(tokenId.toString())} units of token ID ${tokenId}`);

    process.exit();
}

main();

Step 3: Write Get NFT Info Script

Create a file named getNFTInfo.js and add the script for getting NFT information.

const {
    TokenNftInfoQuery,
    Client,
    PrivateKey,
    NftId,
    TokenId,
    AccountId
} = require("@hashgraph/sdk");
require('dotenv').config({ path: 'Token_Service/.env' });

const myAccountId = process.env.MY_ACCOUNT_ID;
const myPrivateKey = PrivateKey.fromString(process.env.MY_PRIVATE_KEY);

const tokenId = process.env.NFT_ID;

// The index of the NFT on the token object - this is the actual NFT
const NFTTokenIndex = 1;

// If we weren't able to grab it, we should throw a new error
if (myAccountId == null ||
    myPrivateKey == null ) {
    throw new Error("Environment variables myAccountId and myPrivateKey must be present");
}

// Create our connection to the Hedera network
// The Hedera JS SDK makes this really easy!
const client = Client.forTestnet();

client.setOperator(myAccountId, myPrivateKey);

async function main() {
    console.log(`Searching for NFT ID ${NFTTokenIndex} on token ${tokenId}`);
    
    //Returns the info for the specified NFT ID
    const nftInfos = await new TokenNftInfoQuery()
        .setNftId(new NftId(TokenId.fromString(tokenId), NFTTokenIndex))
        .execute(client);

    console.log("The ID of the token is: " + nftInfos[0].nftId.tokenId.toString());
    console.log("The serial of the token is: " + nftInfos[0].nftId.serial.toString());
    console.log("The metadata of the token is: " + nftInfos[0].metadata.toString());
    console.log("Current owner: " + new AccountId(nftInfos[0].accountId).toString());

    process.exit();
}

main();

Step 4: Write NFT Mint and Transfer Script

Create a file named mintTransferNFT.js and add the script for minting and transferring NFTs.

const {
    TokenMintTransaction,
    Client,
    PrivateKey,
    AccountBalanceQuery, TokenAssociateTransaction, TransferTransaction, Wallet
} = require("@hashgraph/sdk");
require('dotenv').config({ path: 'Token_Service/.env' });

const myAccountId = process.env.MY_ACCOUNT_ID;
const myPrivateKey = PrivateKey.fromString(process.env.MY_PRIVATE_KEY);

const otherAccountId = process.env.OTHER_ACCOUNT_ID;
const otherPrivateKey = PrivateKey.fromString(process.env.OTHER_PRIVATE_KEY);

const tokenId = process.env.NFT_ID;

// If we weren't able to grab it, we should throw a new error
if (myAccountId == null ||
    myPrivateKey == null ) {
    throw new Error("Environment variables myAccountId and myPrivateKey must be present");
}

// Create our connection to the Hedera network
// The Hedera JS SDK makes this really easy!
const client = Client.forTestnet();

client.setOperator(myAccountId, myPrivateKey);

const wallet = new Wallet(
    otherAccountId,
    otherPrivateKey
);

async function main() {
    //IPFS content identifiers for which we will create a NFT
    CID = "bafybeig5vygdwxnahwgp7vku6kyz4e3hdjsg4uikfz5sujbsummozw3wp4";

    // Mint new NFT
    let mintTx = await new TokenMintTransaction()
        .setTokenId(tokenId)
        .setMetadata([Buffer.from(CID)])
        .freezeWith(client);

    //Sign the transaction with the supply key
    let mintTxSign = await mintTx.sign(myPrivateKey);

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

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

    //Log the serial number
    console.log(`- Created NFT ${tokenId} with serial: ${mintRx.serials[0].low} \n`);

    let balanceCheckTx = await new AccountBalanceQuery().setAccountId(myAccountId).execute(client);

    console.log(`- User balance: ${balanceCheckTx.tokens._map.get(tokenId.toString())} units of token ID ${tokenId}`);

    //  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.
    let associateBuyerTx = await new TokenAssociateTransaction()
        .setAccountId(wallet.accountId)
        .setTokenIds([tokenId])
        .freezeWith(client)
        .sign(otherPrivateKey)

    //SUBMIT THE TRANSACTION
    let associateBuyerTxSubmit = await associateBuyerTx.execute(client);

    //GET THE RECEIPT OF THE TRANSACTION
    let associateBuyerRx = await associateBuyerTxSubmit.getReceipt(client);

    //LOG THE TRANSACTION STATUS
    console.log(`- Token association with the users account: ${associateBuyerRx.status} \n`);

    // Check the balance before the transfer for the treasury account
    balanceCheckTx = await new AccountBalanceQuery().setAccountId(myAccountId).execute(client);
    console.log(`- Treasury balance: ${balanceCheckTx.tokens._map.get(tokenId.toString())} NFTs of ID ${tokenId}`);

    // Check the balance before the transfer for Alice's account
    balanceCheckTx = await new AccountBalanceQuery().setAccountId(otherAccountId).execute(client);
    console.log(`- Buyer's balance: ${balanceCheckTx.tokens._map.get(tokenId.toString())} NFTs of ID ${tokenId}`);

    // Transfer the NFT from treasury to Alice
    // Sign with the treasury key to authorize the transfer
    let tokenTransferTx = await new TransferTransaction()
        .addNftTransfer(tokenId, 1, myAccountId, otherAccountId)
        .freezeWith(client)
        .sign(myPrivateKey);

    let tokenTransferSubmit = await tokenTransferTx.execute(client);
    let tokenTransferRx = await tokenTransferSubmit.getReceipt(client);

    console.log(`\n- NFT transfer from Treasury to Buyer: ${tokenTransferRx.status} \n`);

    // Check the balance of the treasury account after the transfer
    balanceCheckTx = await new AccountBalanceQuery().setAccountId(myAccountId).execute(client);
    console.log(`- Treasury balance: ${balanceCheckTx.tokens._map.get(tokenId.toString())} NFTs of ID ${tokenId}`);

    // Check the balance of Alice's account after the transfer
    balanceCheckTx = await new AccountBalanceQuery().setAccountId(otherAccountId).execute(client);
    console.log(`- Buyer's balance: ${balanceCheckTx.tokens._map.get(tokenId.toString())} NFTs of ID ${tokenId}`);

    process.exit();
}

main();

Step 5: Write NFT Transfer and Burn Script

Create a file named transferBurnNFT.js and add the script for transferring and burning NFTs.

const {
    TokenBurnTransaction,
    Client,
    PrivateKey,
    AccountBalanceQuery,
    TransferTransaction
} = require("@hashgraph/sdk");
require('dotenv').config({ path: 'Token_Service/.env' });

const myAccountId = process.env.MY_ACCOUNT_ID;
const myPrivateKey = PrivateKey.fromString(process.env.MY_PRIVATE_KEY);

const otherAccountId = process.env.OTHER_ACCOUNT_ID;
const otherPrivateKey = PrivateKey.fromString(process.env.OTHER_PRIVATE_KEY);

const tokenId = process.env.NFT_ID;

// If we weren't able to grab it, we should throw a new error
if (myAccountId == null ||
    myPrivateKey == null ) {
    throw new Error("Environment variables myAccountId and myPrivateKey must be present");
}

// Create our connection to the Hedera network
// The Hedera JS SDK makes this really easy!
const client = Client.forTestnet();

client.setOperator(myAccountId, myPrivateKey);

async function main() {

    // Check the balance before the transfer for the treasury account
    balanceCheckTx = await new AccountBalanceQuery().setAccountId(myAccountId).execute(client);
    console.log(`- Treasury balance: ${balanceCheckTx.tokens._map.get(tokenId.toString())} NFTs of ID ${tokenId}`);

    // Check the balance before the transfer for the buyer's account
    balanceCheckTx = await new AccountBalanceQuery().setAccountId(otherAccountId).execute(client);
    console.log(`- Buyer's balance: ${balanceCheckTx.tokens._map.get(tokenId.toString())} NFTs of ID ${tokenId}`);

    // Transfer the NFT from treasury to the buyer
    // Sign with the treasury key to authorize the transfer
    let tokenTransferTx = await new TransferTransaction()
        .addNftTransfer(tokenId, 1, otherAccountId, myAccountId)
        .freezeWith(client)
        .sign(otherPrivateKey);

    let tokenTransferSubmit = await tokenTransferTx.execute(client);
    let tokenTransferRx = await tokenTransferSubmit.getReceipt(client);

    console.log(`\n- NFT transfer from Buyer to Treasury: ${tokenTransferRx.status} \n`);

    // Check the balance of the treasury account after the transfer
    balanceCheckTx = await new AccountBalanceQuery().setAccountId(myAccountId).execute(client);
    console.log(`- Treasury balance: ${balanceCheckTx.tokens._map.get(tokenId.toString())} NFTs of ID ${tokenId}`);

    // Check the balance of the buyer's account after the transfer
    balanceCheckTx = await new AccountBalanceQuery().setAccountId(otherAccountId).execute(client);
    console.log(`- Buyer's balance: ${balanceCheckTx.tokens._map.get(tokenId.toString())} NFTs of ID ${tokenId}`);

    //Burn the nft and freeze the unsigned transaction for manual signing
    const transaction = await new TokenBurnTransaction()
        .setTokenId(tokenId)
        .setSerials([1])
        .freezeWith(client);

    //Sign with the supply private key of the token
    const signTx = await transaction.sign(myPrivateKey);

    //Submit the transaction to a Hedera network
    const txResponse = await signTx.execute(client);

    //Request the receipt of the transaction
    const receipt = await txResponse.getReceipt(client);

    //Get the transaction consensus status
    const transactionStatus = receipt.status;

    console.log("The transaction consensus status " +transactionStatus.toString());

    // Check the balance of the treasury account after the transfer
    balanceCheckTx = await new AccountBalanceQuery().setAccountId(myAccountId).execute(client);
    console.log(`- Treasury balance: ${balanceCheckTx.tokens._map.get(tokenId.toString())} NFTs of ID ${tokenId}`);

    // Check the balance of the buyer's account after the transfer
    balanceCheckTx = await new AccountBalanceQuery().setAccountId(otherAccountId).execute(client);
    console.log(`- Buyer's balance: ${balanceCheckTx.tokens._map.get(tokenId.toString())} NFTs of ID ${tokenId}`);

    process.exit();
}

main();

Example Metadata

In your non-fungible token scripts, you can customize the token properties using metadata. An example metadata file, metadataExample.json, is provided below. This file contains sample metadata that you can use as a template or modify according to your specific requirements.

Feel free to explore and utilize the information in metadataExample.json to enhance the details associated with your non-fungible tokens during creation, minting, or other relevant operations. Remember to add the relevant code in required script for reading this metadata file.

Create a file named metadataExample.json and add the below script.

{
  "name": "Ticket #1",
  "description": "This is one of the few tickets to THE event.",
  "image": "https://i.pinimg.com/originals/f9/ef/83/f9ef835e39ed1f52c1c89109c7c330fd.jpg",
  "properties": {
    "latitude": "37ยฐ14'09.8N",
    "Longitude": "115ยฐ48'03.9W",
    "date": "22.12.2051",
    "info": "bring some pineapples"
  }
}

Step 6: Run the Scripts

Execute each script in the order of NFT creation, getting NFT info, minting and transferring, and transferring and burning. Ensure that you are following the proper sequence.

node createNFT.js
node getNFTInfo.js
node mintTransferNFT.js
node transferBurnNFT.js

Conclusion

Congratulations! You have successfully executed scripts for various Hedera Token Service operations related to non-fungible tokens (NFTs). This tutorial covers fundamental steps, and you can further explore advanced features and integrations based on your specific use case.

Last updated