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:
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' });constmyAccountId=process.env.MY_ACCOUNT_ID;constmyPrivateKey=PrivateKey.fromString(process.env.MY_PRIVATE_KEY);// If we weren't able to grab it, we should throw a new errorif (myAccountId ==null|| myPrivateKey ==null ) {thrownewError("Environment variables myAccountId and myPrivateKey must be present");}// Create our connection to the Hedera network// The Hedera JS SDK makes this really easy!constclient=Client.forTestnet();client.setOperator(myAccountId, myPrivateKey);asyncfunctionmain() {//Create the NFTlet nftCreate =awaitnewTokenCreateTransaction().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 keylet nftCreateTxSign =awaitnftCreate.sign(myPrivateKey);//Submit the transaction to a Hedera networklet nftCreateSubmit =awaitnftCreateTxSign.execute(client);//Get the transaction receiptlet nftCreateRx =awaitnftCreateSubmit.getReceipt(client);//Get the token IDlet tokenId =nftCreateRx.tokenId;//Log the token IDconsole.log(`- Created NFT with Token ID: ${tokenId} \n`);constbalanceCheckTx=awaitnewAccountBalanceQuery().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' });constmyAccountId=process.env.MY_ACCOUNT_ID;constmyPrivateKey=PrivateKey.fromString(process.env.MY_PRIVATE_KEY);consttokenId=process.env.NFT_ID;// The index of the NFT on the token object - this is the actual NFTconstNFTTokenIndex=1;// If we weren't able to grab it, we should throw a new errorif (myAccountId ==null|| myPrivateKey ==null ) {thrownewError("Environment variables myAccountId and myPrivateKey must be present");}// Create our connection to the Hedera network// The Hedera JS SDK makes this really easy!constclient=Client.forTestnet();client.setOperator(myAccountId, myPrivateKey);asyncfunctionmain() {console.log(`Searching for NFT ID ${NFTTokenIndex} on token ${tokenId}`);//Returns the info for the specified NFT IDconstnftInfos=awaitnewTokenNftInfoQuery().setNftId(newNftId(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: "+newAccountId(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' });constmyAccountId=process.env.MY_ACCOUNT_ID;constmyPrivateKey=PrivateKey.fromString(process.env.MY_PRIVATE_KEY);constotherAccountId=process.env.OTHER_ACCOUNT_ID;constotherPrivateKey=PrivateKey.fromString(process.env.OTHER_PRIVATE_KEY);consttokenId=process.env.NFT_ID;// If we weren't able to grab it, we should throw a new errorif (myAccountId ==null|| myPrivateKey ==null ) {thrownewError("Environment variables myAccountId and myPrivateKey must be present");}// Create our connection to the Hedera network// The Hedera JS SDK makes this really easy!constclient=Client.forTestnet();client.setOperator(myAccountId, myPrivateKey);constwallet=newWallet( otherAccountId, otherPrivateKey);asyncfunctionmain() {//IPFS content identifiers for which we will create a NFTCID="bafybeig5vygdwxnahwgp7vku6kyz4e3hdjsg4uikfz5sujbsummozw3wp4";// Mint new NFTlet mintTx =awaitnewTokenMintTransaction().setTokenId(tokenId).setMetadata([Buffer.from(CID)]).freezeWith(client);//Sign the transaction with the supply keylet mintTxSign =awaitmintTx.sign(myPrivateKey);//Submit the transaction to a Hedera networklet mintTxSubmit =awaitmintTxSign.execute(client);//Get the transaction receiptlet mintRx =awaitmintTxSubmit.getReceipt(client);//Log the serial numberconsole.log(`- Created NFT ${tokenId} with serial: ${mintRx.serials[0].low} \n`);let balanceCheckTx =awaitnewAccountBalanceQuery().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 =awaitnewTokenAssociateTransaction().setAccountId(wallet.accountId).setTokenIds([tokenId]).freezeWith(client).sign(otherPrivateKey)//SUBMIT THE TRANSACTIONlet associateBuyerTxSubmit =awaitassociateBuyerTx.execute(client);//GET THE RECEIPT OF THE TRANSACTIONlet associateBuyerRx =awaitassociateBuyerTxSubmit.getReceipt(client);//LOG THE TRANSACTION STATUSconsole.log(`- Token association with the users account: ${associateBuyerRx.status} \n`);// Check the balance before the transfer for the treasury account balanceCheckTx =awaitnewAccountBalanceQuery().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 =awaitnewAccountBalanceQuery().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 transferlet tokenTransferTx =awaitnewTransferTransaction().addNftTransfer(tokenId,1, myAccountId, otherAccountId).freezeWith(client).sign(myPrivateKey);let tokenTransferSubmit =awaittokenTransferTx.execute(client);let tokenTransferRx =awaittokenTransferSubmit.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 =awaitnewAccountBalanceQuery().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 =awaitnewAccountBalanceQuery().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' });constmyAccountId=process.env.MY_ACCOUNT_ID;constmyPrivateKey=PrivateKey.fromString(process.env.MY_PRIVATE_KEY);constotherAccountId=process.env.OTHER_ACCOUNT_ID;constotherPrivateKey=PrivateKey.fromString(process.env.OTHER_PRIVATE_KEY);consttokenId=process.env.NFT_ID;// If we weren't able to grab it, we should throw a new errorif (myAccountId ==null|| myPrivateKey ==null ) {thrownewError("Environment variables myAccountId and myPrivateKey must be present");}// Create our connection to the Hedera network// The Hedera JS SDK makes this really easy!constclient=Client.forTestnet();client.setOperator(myAccountId, myPrivateKey);asyncfunctionmain() {// Check the balance before the transfer for the treasury account balanceCheckTx =awaitnewAccountBalanceQuery().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 =awaitnewAccountBalanceQuery().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 transferlet tokenTransferTx =awaitnewTransferTransaction().addNftTransfer(tokenId,1, otherAccountId, myAccountId).freezeWith(client).sign(otherPrivateKey);let tokenTransferSubmit =awaittokenTransferTx.execute(client);let tokenTransferRx =awaittokenTransferSubmit.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 =awaitnewAccountBalanceQuery().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 =awaitnewAccountBalanceQuery().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 signingconsttransaction=awaitnewTokenBurnTransaction().setTokenId(tokenId).setSerials([1]).freezeWith(client);//Sign with the supply private key of the tokenconstsignTx=awaittransaction.sign(myPrivateKey);//Submit the transaction to a Hedera networkconsttxResponse=awaitsignTx.execute(client);//Request the receipt of the transactionconstreceipt=awaittxResponse.getReceipt(client);//Get the transaction consensus statusconsttransactionStatus=receipt.status;console.log("The transaction consensus status "+transactionStatus.toString());// Check the balance of the treasury account after the transfer balanceCheckTx =awaitnewAccountBalanceQuery().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 =awaitnewAccountBalanceQuery().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.
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.