Hedera
  • Welcome to Hedera — let’s build the future
  • Getting Started
    • Environment Setup
    • Web2 Developers
      • Transfer HBAR
      • Create a Token
      • Create a Topic
    • EVM Developers
      • Deploy a Contract
  • Tutorials
    • Smart Contracts
      • How to Mint & Burn an ERC-721 Token Using Hardhat and Ethers (Part 1)
      • How to Set Access Control, a Token URI, Pause, and Transfer an ERC-721 Token Using Hardhat (Part 2)
      • How to Verify a Smart Contract on HashScan
      • Deploy a Smart Contract Using Remix
      • Deploy a Smart Contract Using Hardhat and Hiero JSON-RPC Relay
      • Deploy Your First Smart Contract
      • Deploy a Contract Using the Hedera Token Service
      • Send and Receive HBAR Using Solidity Smart Contracts
      • Deploy By Leveraging Ethereum Developer Tools On Hedera
      • Deploy a Subgraph Using The Graph and Hedera JSON-RPC Relay
      • Deploy Smart Contracts on Hedera Using Truffle
      • The Power of Native Hedera Tokens as ERC-20 Tokens: A step-by-step guide
      • HTS x EVM - Part 1: How to Mint NFTs
      • HTS x EVM - Part 2: KYC & Update
      • HTS x EVM - Part 3: How to Pause, Freeze, Wipe, and Delete NFTs
      • Hedera Smart Contracts Workshop
        • Setup
        • Solidity
        • Hedera SDK JS
        • Hardhat and EthersJs
        • Outro
      • Foundry
        • How to Setup Foundry and Write a Basic Unit Test
        • How to Deploy and Verify a Hedera Smart Contract with Foundry
        • How to Test A Solidity Event
        • How to Fork Testnet on Latest Block
    • Consensus
      • Submit Your First Message
      • Submit Message to Private Topic
      • Query Messages with Mirror Node
    • Tokens
      • Create and Transfer Your First NFT
      • Create and Transfer Your First Fungible Token
      • Create and Transfer an NFT using a Solidity Contract
      • Structure Your Token Metadata Using JSON Schema V2
      • Hedera Token Service - Part 1: How to Mint NFTs
      • Hedera Token Service - Part 2: KYC, Update, and Scheduled Transactions
      • Hedera Token Service - Part 3: How to Pause, Freeze, Wipe, and Delete NFTs
      • Create Your First Frictionless Airdrop Campaign
    • Local Node
      • How to Run Hedera Local Node in a Cloud Development Environment (CDE)
        • Run a Local Node in Gitpod
        • Run a Local Node in Codespaces
      • How to Set Up a Hedera Local Node
      • Set Up a Hedera Local Node using the NPM CLI
    • More Tutorials
      • Create and Fund Your Hedera Testnet Account
      • How to Create a Personal Access Token (API Key) on the Hedera Portal
      • How to Auto-Create Hedera Accounts with HBAR and Token Transfers
      • How to Configure a Mirror Node and Query Data
      • How to Generate a Random Number on Hedera
      • Get Started with the Hedera Consensus Service Fabric Plugin
        • Virtual Environment Setup
      • Schedule Your First Transaction
      • How to Connect to Hedera Networks Over RPC
        • Configuring Hashio RPC endpoints
        • Configuring Hiero JSON-RPC Relay endpoints
        • Configuring Validation Cloud RPC endpoints
      • JavaScript Testing
      • Create a Hedera DApp Integrated with WalletConnect
      • How to Connect MetaMask to Hedera
    • Demo Applications
    • Starter Projects
    • Building on Hedera (course)
  • Networks
    • Mainnet
      • Mainnet Accounts
      • Mainnet Consensus Nodes
        • Node Requirements
          • FAQ
      • Fees
        • Transaction Records
    • Testnets
      • Testnet Accounts
      • Testnet Consensus Nodes
    • Localnet
      • Single Node Configuration
      • Multinode Configuration
    • Network Explorers and Tools
    • Release Notes
      • Consensus Node
      • Hedera Mirror Node
  • Core Concepts
    • Accounts
      • Account Creation
      • Auto Account Creation
      • Account Properties
    • Keys and Signatures
    • Schedule Transaction
    • Smart Contracts
      • Understanding Hedera's EVM Differences and Compatibility
        • For EVM Developers Migrating to Hedera
          • Accounts, Signature Verification & Keys (ECDSA vs. ED25519)
          • JSON-RPC Relay and EVM Tooling
          • Token Management with Hedera Token Service
          • Decimal Handling (8 vs. 18 Decimals)
          • Handling HBAR Transfers in Contracts
        • For Hedera-Native Developers Adding Smart Contract Functionality
          • Integrating ED25519 Accounts and Advanced Features Into Smart Contracts
          • JSON-RPC Relay and State Queries
          • Extending Token Management with Smart Contracts
      • Creating Smart Contracts
      • Compiling Smart Contracts
      • System Smart Contracts
        • Hedera Account Service
        • Hedera Schedule Service
      • Gas and Fees
      • JSON-RPC Relay
      • Deploying Smart Contracts
      • Smart Contract Addresses
      • Verifying Smart Contracts
      • Smart Contract Traceability
      • Tokens Managed by Smart Contracts
        • ERC-20 (Fungible Tokens)
        • ERC-721 (Non-Fungible Token)
        • ERC-3643 Real World Assets (RWA)
        • ERC-1363 (Payable Tokens)
        • Hedera Token Service System Contract
      • Wrapped HBAR (WHBAR)
      • Smart Contract Rent
      • Smart Contract Security
      • EVM Archive Node Queries
    • Tokens
      • Tokenization on Hedera
      • Hedera Token Service (HTS) Native Tokenization
        • Token Types and ID Formats
        • Token Properties
        • Token Creation
        • Custom Fee Schedule
        • Token Airdrops
      • ERC/EVM-Compatible Tokenization
      • Hybrid (HTS + EVM ) Tokenization
    • Staking
      • Staking Program
      • Stake HBAR
    • Hashgraph Consensus Algorithm
      • Gossip About Gossip
      • Virtual Voting
    • Transactions and Queries
      • Transaction Properties
    • State and History
    • Mirror Nodes
      • Hedera Mirror Node
      • One Click Mirror Node Deployment
      • Run Your Own Mirror Node
        • Run Your Own Mirror Node with Google Cloud Storage (GCS)
        • Run Your Mirror Node with Amazon Web Services S3 (AWS)
  • Open Source Solutions and Integrations
    • AI Tools for Developers
      • Hedera AI Agent Kit
      • ElizaOS Plugin for Hedera
      • Hedera Hivemind
      • Kapa AI
    • Asset Tokenization Studio (ATS)
      • Web User Interface (UI)
      • Frequently Asked Questions (FAQs)
    • HashioDAO
      • Governance Token DAO
      • NFT DAO
      • Multisig DAO
      • DAO Proposals
      • Local Environment Setup
    • Hedera CLI
    • Hedera Custodians Library
      • How to use it
    • Hedera Developer Playground
    • Hedera Wallet Snap By MetaMask
      • Hedera Wallet Snap Documentation
      • Tutorial: MetaMask Snaps – What Are They and How to Use Them
    • Interoperability and Bridging
      • LayerZero
    • NFT Studio
      • Airdrop List Verifier
      • Metadata Validator
      • NFT Rarity Inspector
      • NFT Token Holders List Builder
      • NFT Risk Calculator
      • Token Balance Snapshot
      • Hedera NFT SDK
    • Oracle Networks
      • Chainlink Oracles
      • Pyth Oracles
      • Supra Oracles
    • Stablecoin Studio
      • Core Concepts
      • Web UI Application
      • CLI Management
      • TypeScript SDK
    • Hedera Guardian
    • Hedera WalletConnect
  • SDKs & APIs
    • SDKs
      • Build Your Hedera Client
      • Set Up Your Local Network
      • Network Address Book
      • Keys
        • Generate a new key pair
        • Import an existing key
        • Create a key list
        • Create a threshold key
        • Generate a mnemonic phrase
        • Recover keys from a mnemonic phrase
      • HBAR
      • Specialized Types
      • Pseudorandom Number Generator
      • Transactions
        • Create a Batch Transaction
        • Transaction ID
        • Modify transaction fields
        • Create an unsigned transaction
        • Manually sign a transaction
        • Submit a transaction
        • Sign a multisignature transaction
        • Get a transaction receipt
        • Get a transaction record
      • Schedule Transaction
        • Schedule ID
        • Create a schedule transaction
        • Sign a scheduled transaction
        • Delete a schedule transaction
        • Get schedule info
        • Network Response Messages
      • Queries
      • General Network Response Messages
      • Accounts and HBAR
        • Create an account
        • Update an account
        • Transfer cryptocurrency
        • Approve an allowance
        • Delete an allowance
        • Delete an account
        • Get account balance
        • Get account info
        • Network Response Messages
      • Consensus Service
        • Create a topic
        • Update a topic
        • Submit a message
        • Delete a topic
        • Get topic messages
        • Get topic info
        • Network Response
      • Token Service
        • Token ID
        • NFT ID
        • Token types
        • Create a token
        • Custom token fees
        • Update a token
        • Update token custom fees
        • Update NFT metadata
        • Transfer tokens
        • Airdrop a token
        • Claim a token
        • Cancel a token
        • Reject a token
        • Delete a token
        • Mint a token
        • Burn a token
        • Freeze an account
        • Unfreeze an account
        • Enable KYC account flag
        • Disable KYC account flag
        • Associate tokens to an account
        • Dissociate tokens from an account
        • Pause a token
        • Unpause a token
        • Wipe a token
        • Atomic swaps
        • Get account token balance
        • Get token info
        • Get NFT info
        • Network Response Messages
      • File Service
        • Create a file
        • Append to a file
        • Update a file
        • Delete a file
        • Get file contents
        • Get file info
        • Network Response Messages
      • Smart Contract Service
        • Delegate Contract ID
        • Create a smart contract
        • Update a smart contract
        • Delete a smart contract
        • Call a smart contract function
        • Ethereum transaction
        • Get a smart contract function
        • Get smart contract bytecode
        • Get smart contract info
        • Hedera Service Solidity Libraries
        • Network Response Messages
      • Signature Provider
        • Provider
        • Signer
        • Wallet
        • Local Provider
    • Mirror Node REST API
      • Accounts
      • Balances
      • Blocks
      • Schedule Transactions
      • Smart Contracts
      • Tokens
      • Topics
      • Transactions
      • Network
    • Hedera Consensus Service gRPC API
    • Hedera APIs
      • Basic Types
        • AccountAmount
        • AccountID
        • ContractID
        • CryptoAllowance
        • CurrentAndNextFeeSchedule
        • FeeComponents
        • FeeData
        • FeeSchedule
        • FileID
        • Fraction
        • HederaFunctionality
        • Key
        • KeyList
        • NftAllowance
        • NftTransfer
        • NodeAddress
        • NodeAddressBook
        • RealmID
        • ScheduleID
        • SemanticVersion
        • ServicesConfigurationList
        • ServiceEndpoint
        • Setting
        • ShardID
        • Signature
        • SignatureList
        • SignatureMap
        • SignaturePair
        • SubType
        • TransferList
        • TransactionID
        • ThresholdKey
        • ThresholdSignature
        • TokenAllowance
        • TokenBalance
        • TokenBalances
        • TokenFreezeStatus
        • TokenPauseStatus
        • TokenID
        • TokenKycStatus
        • TokenRelationship
        • TokenTransferList
        • TokenType
        • TokenSupplyType
        • TopicID
        • TransactionFeeSchedule
      • Cryptocurrency Accounts
        • CryptoService
        • CryptApproveAllowance
        • CryptoDeleteAllowance
        • CryptoCreate
        • CryptoTransfer
        • CryptoUpdate
        • CryptoDelete
        • CryptoGetAccountBalance
        • CryptoGetAccountRecords
        • CryptoGetInfo
        • CryptoGetStakers
      • Consensus Service
        • Consensus Service
        • ConsensusCreateTopic
        • ConsensusUpdateTopic
        • ConsensusSubmitMessage
        • ConsensusDeleteTopic
        • ConsensusTopicInfo
        • ConsensusGetTopicInfo
      • Schedule Service
        • ScheduleService
        • SchedulableTransactionBody
        • ScheduleCreate
        • ScheduleDelete
        • ScheduleSign
        • ScheduleGetInfo
      • Token Service
        • TokenService
        • CustomFees
          • AssessedCustomFee
          • CustomFee
          • FractionalFee
          • FixedFee
          • RoyaltyFee
        • TokenCreate
        • TokenUpdate
        • TokenFeeScheduleUpdate
        • TokenDelete
        • TokenMint
        • TokenBurn
        • TokenFreezeAccount
        • TokenUnfreezeAccount
        • TokenGrantKyc
        • TokenRevokeKyc
        • TokenAssociate
        • TokenDissociate
        • TokenWipeAccount
        • TokenPause
        • TokenUnpause
        • TokenGetInfo
        • TokenGetNftInfo
        • TokenGetNftInfos
        • TokenGetAccountNftInfo
      • File Service
        • FileService
        • FileCreate
        • FileAppend
        • FileUpdate
        • FileDelete
        • FileGetContents
        • FileGetInfo
      • Smart Contracts
        • SmartContractService
        • ContractCall
        • ContractCallLocal
        • ContractCreate
        • ContractUpdate
        • ContractDelete
        • ContractGetByteCode
        • ContractGetInfo
        • ContractGetRecords
      • Miscellaneous
        • Duration
        • ExchangeRate
        • Freeze
        • FreezeType
        • GetByKey
        • GetBySolidityID
        • NetworkGetVersionInfo
        • NetworkService
        • Query
        • QueryHeader
        • Response
        • ResponseCode
        • ResponseHeader
        • SystemDelete
        • SystemUndelete
        • TimeStamp
        • Transaction
        • TransactionBody
        • TransactionContents
        • TransactionGetFastRecord
        • TransactionGetReceipt
        • TransactionGetRecord
        • TransactionReceipt
        • TransactionRecord
        • TransactionResponse
        • UncheckedSubmit
    • Hedera Status API
  • Support & Community
    • Glossary
    • Contributing to Hedera documentation
      • Contribution Guidelines
        • Creating Issues
        • Creating Pull Requests
        • Hedera Improvement Proposal (HIP)
        • Submit Demo Applications
      • Style Guide
        • Understanding different types of documentation
        • Use of HBAR and tinybars
        • Use of web2 and web3
        • Language and grammar
        • Formatting
        • Punctuation
        • GitBook Markdown Syntax
    • Discord
    • GitHub
    • Stack Overflow
    • Hedera Blog
    • Bug Bounty
    • Hedera Help
    • Documentation Survey
    • Meetups
    • Brand Guidelines
    • Status Page
Powered by GitBook
On this page
  • Prerequisites
  • Create New Hedera Accounts and Generate Keys for the NFT
  • Create a Custom Fee Schedule
  • Create a Non-Fungible Token (NFT)
  • Mint and Burn NFTs
  • Auto-Associate and Transfer NFTs
  • NFT Transfer from Treasury to Alice
  • NFT Transfer from Alice to Bob
  • Conclusion
  • Additional Resources

Was this helpful?

Edit on GitHub
  1. Tutorials
  2. Tokens

Hedera Token Service - Part 1: How to Mint NFTs

PreviousStructure Your Token Metadata Using JSON Schema V2NextHedera Token Service - Part 2: KYC, Update, and Scheduled Transactions

Last updated 4 months ago

Was this helpful?

LogoLogo

INTRODUCTION

  • Fees
  • Core Concepts
  • Network Information

TOOLS

  • Bridge
  • Oracles
  • Explorers
  • Developer Portal & Faucet

RESOURCES

  • Status
  • Bug Bounty
  • Build on Hedera (course)
  • Documentation Survey

Hedera Token Service (HTS) enables you to configure, mint, and manage tokens on the Hedera network without the need to set up and deploy a smart contract. Tokens are as fast, fair, and secure as HBAR and cost a fraction of 1¢ USD to transfer.

Let’s look at some of the functionality available to you with HTS. You will see that the ease of use and flexibility this service provides make HTS tokens an excellent alternative to tokens with smart contracts on Ethereum. For those coming from Ethereum, HTS functionality can be mapped to multiple types of ERC token standards, including ERC20, ERC721, and ERC1155 – you can learn more about the mapping in this . Starting in early 2022, you can use HTS with smart contracts for cases needing advanced logic and programmability for your tokens.

In this part of the series, you will learn how to:

  • Create a custom fee schedule

  • Configure a non-fungible token (NFT)

  • Mint and burn NFTs

  • Associate and Transfer NFTs

We will configure an NFT art collection for autumn images. With HTS, you can also create fungible tokens representing anything from a stablecoin pegged to the USD value to an in-game reward system.

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.

Create New Hedera Accounts and Generate Keys for the NFT

Once accounts are created for Treasury, Alice, and Bob, new private keys are generated to manage specific token functionality. Always provide the corresponding public key when specifying the key value for specific functionality.

Never expose or share your private key(s) with others, as that may result in lost funds or loss of control over your account.

nft-part1.js
// CREATE NEW HEDERA ACCOUNTS TO REPRESENT OTHER USERS
const initBalance = new Hbar(200);
const treasuryKey = PrivateKey.generateECDSA();
const [treasurySt, treasuryId] = await accountCreateFcn(
  treasuryKey,
  initBalance,
  client
);
console.log(`- Treasury's account: https://hashscan.io/testnet/account/${treasuryId}`);
const aliceKey = PrivateKey.generateECDSA();
const [aliceSt, aliceId] = await accountCreateFcn(aliceKey, initBalance, client);
console.log(`- Alice's account: https://hashscan.io/testnet/account/${aliceId}`);
const bobKey = PrivateKey.generateECDSA();
const [bobSt, bobId] = await accountCreateFcn(bobKey, initBalance, client);
console.log(`- Bob's account: https://hashscan.io/testnet/account/${bobId}`);

// GENERATE KEYS TO MANAGE FUNCTIONAL ASPECTS OF THE TOKEN
const supplyKey = PrivateKey.generate();
const adminKey = PrivateKey.generate();
const pauseKey = PrivateKey.generate();
const freezeKey = PrivateKey.generate();
const wipeKey = PrivateKey.generate();
const kycKey = PrivateKey.generate();
const newKycKey = PrivateKey.generate();
nft-part1.js
// ACCOUNT CREATOR FUNCTION ==========================================
async function accountCreateFcn(pvKey, iBal, client) {
  const response = await new AccountCreateTransaction()
    .setInitialBalance(iBal)
    .setKey(pvKey.publicKey)
    .setMaxAutomaticTokenAssociations(10)
    .execute(client);
  const receipt = await response.getReceipt(client);
  return [receipt.status, receipt.accountId];
}

Console output:

- Treasury's account: https://hashscan.io/testnet/account/0.0.4672116
- Alice's account: https://hashscan.io/testnet/account/0.0.4672117
- Bob's account: https://hashscan.io/testnet/account/0.0.4672118

Create a Custom Fee Schedule

nft-part1.js
// DEFINE CUSTOM FEE SCHEDULE
let nftCustomFee = await new CustomRoyaltyFee()
  .setNumerator(1)
  .setDenominator(10)
  .setFeeCollectorAccountId(treasuryId)
  .setFallbackFee(new CustomFixedFee().setHbarAmount(new Hbar(200)));

Create a Non-Fungible Token (NFT)

These are the images for our NFT collection.

These are the content identifiers (CIDs) for the NFT metadata, which points to the images, and below is a sample of the metadata:

nft-part1.js
// IPFS CONTENT IDENTIFIERS FOR WHICH WE WILL CREATE NFTs - SEE uploadJsonToIpfs.js
let CIDs = [
    Buffer.from("ipfs://bafkreibr7cyxmy4iyckmlyzige4ywccyygomwrcn4ldcldacw3nxe3ikgq"),
    Buffer.from("ipfs://bafkreig73xgqp7wy7qvjwz33rp3nkxaxqlsb7v3id24poe2dath7pj5dhe"),
    Buffer.from("ipfs://bafkreigltq4oaoifxll3o2cc3e3q3ofqzu6puennmambpulxexo5sryc6e"),
    Buffer.from("ipfs://bafkreiaoswszev3uoukkepctzpnzw56ey6w3xscokvsvmfrqdzmyhas6fu"),
    Buffer.from("ipfs://bafkreih6cajqynaqwbrmiabk2jxpy56rpf25zvg5lbien73p5ysnpehyjm"),
];
{
  "name": "LEAF1",
  "creator": "Mother Nature & Hashgraph",
  "description": "Autumn",
  "image": "ipfs://Qmb3CMWJzxWZJ34TgJgjASvdTc4x6PEz6LGm2QTWPPpkw5",
  "type": "image/jpg",
  "format": "HIP412@2.0.0",
  "properties": {
    "city": "Boston",
    "season": "Fall",
    "decade": "20's",
    "license": "MIT-0",
    "collection": "Fall Collection",
    "website": "www.hashgraph.com"
  }
}

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.

nft-part1.js
// CREATE NFT WITH CUSTOM FEE
let nftCreateTx = await new TokenCreateTransaction()
    .setTokenName("Fall Collection")
    .setTokenSymbol("LEAF")
    .setTokenType(TokenType.NonFungibleUnique)
    .setDecimals(0)
    .setInitialSupply(0)
    .setTreasuryAccountId(treasuryId)
    .setSupplyType(TokenSupplyType.Finite)
    .setMaxSupply(CIDs.length)
    .setCustomFees([nftCustomFee])
    .setAdminKey(adminKey)
    .setSupplyKey(supplyKey)
    .setPauseKey(pauseKey)
    .setFreezeKey(freezeKey)
    .setWipeKey(wipeKey)
    .freezeWith(client)
    .sign(treasuryKey);

let nftCreateTxSign = await nftCreateTx.sign(adminKey)
let nftCreateSubmit = await nftCreateTxSign.execute(client);
let nftCreateRx = await nftCreateSubmit.getReceipt(client);
let tokenId = nftCreateRx.tokenId;
console.log(`\n- Created NFT with Token ID: ${tokenId}`);
console.log(
  `- See: https://hashscan.io/${network}/transaction/${nftCreateSubmit.transactionId}`
);

// TOKEN QUERY TO CHECK THAT THE CUSTOM FEE SCHEDULE IS ASSOCIATED WITH NFT
var tokenInfo = await new TokenInfoQuery().setTokenId(tokenId).execute(client);
console.log(` `);
console.table(tokenInfo.customFees[0]);

Console output:

- Created NFT with Token ID: 0.0.4672119
- See: https://hashscan.io/testnet/transaction/0.0.4649505@1723230932.976832324

Mint and Burn NFTs

In terms of use cases, you may want to mint new NFTs to add items to your NFT class, or you may need to burn NFTs to take a specific item out of circulation. Alternatively, if you’re working with a fungible token (like a stablecoin), you may want to mint new tokens every time there is a new deposit and burn tokens anytime that someone converts their tokens back into fiat.

In this case we’re creating a batch of five NFTs for a collection of five images. We’ll use a “token minter” function and a for loop to speed up the batch NFT minting from our array of content identifiers (CID array):

nft-part1.js
// MINT NEW BATCH OF NFTs - CAN MINT UP TO 10 NFT SERIALS IN A SINGLE TRANSACTION
let [nftMintRx, mintTxId] = await tokenMinterFcn(CIDs);
console.log(
  `\n- Mint ${CIDs.length} serials for NFT collection ${tokenId}: ${nftMintRx.status}`
);
console.log(`- See: https://hashscan.io/${network}/transaction/${mintTxId}`);
nft-part1.js
// TOKEN MINTER FUNCTION ==========================================
 async function tokenMinterFcn(CIDs) {
    let mintTx = new TokenMintTransaction()
	.setTokenId(tokenId)
	.setMetadata(CIDs)
	.freezeWith(client);
        let mintTxSign = await mintTx.sign(supplyKey);
        let mintTxSubmit = await mintTxSign.execute(client);
        let mintRx = await mintTxSubmit.getReceipt(client);
        return [mintRx, mintTxSubmit.transactionId];
    }

Console output:

- Mint 5 serials for NFT collection 0.0.4672119: SUCCESS
- See: https://hashscan.io/testnet/transaction/0.0.4649505@1723230934.771266715

If you change your mind and decide that you don’t need the last NFT, then you can burn it as follows:

nft-part1.js
// BURN THE LAST NFT IN THE COLLECTION
let tokenBurnTx = await new TokenBurnTransaction()
    .setTokenId(tokenId)
    .setSerials([CIDs.length])
    .freezeWith(client)
    .sign(supplyKey);
    
let tokenBurnSubmit = await tokenBurnTx.execute(client);
let tokenBurnRx = await tokenBurnSubmit.getReceipt(client);
console.log(`\n- Burn NFT with serial ${CIDs.length}: ${tokenBurnRx.status}`);
console.log(
  `- See: https://hashscan.io/${network}/transaction/${tokenBurnSubmit.transactionId}`
 );

var tokenInfo = await new TokenInfoQuery()
    .setTokenId(tokenId)
    .execute(client);
console.log(`\n- Current NFT supply: ${tokenInfo.totalSupply}`);

Console output:

- Burn NFT with serial 5: SUCCESS
- See: https://hashscan.io/testnet/transaction/0.0.4649505@1723230939.588918306
- Current NFT supply: 4

Auto-Associate and Transfer NFTs

Before an account that is not the treasury for a token can receive or send this specific token ID, it must become “associated” with the token. This helps reduce unwanted spam and other concerns from users who don’t want to be associated with any of the variety of tokens created on the Hedera network.

  • Alice’s account will be updated to associate with the token automatically

  • Bob’s account will be manually associated with the token ID

nft-part1.js
// AUTO-ASSOCIATION FOR ALICE'S ACCOUNT
let associateTx = await new AccountUpdateTransaction()
    .setAccountId(aliceId)
    .setMaxAutomaticTokenAssociations(10)
    .freezeWith(client)
    .sign(aliceKey);
let associateTxSubmit = await associateTx.execute(client);
let associateRx = await associateTxSubmit.getReceipt(client);
console.log(`\n- Alice NFT Auto-Association: ${associateRx.status}`);
console.log(
  `- See: https://hashscan.io/${network}/transaction/${associateTxSubmit.transactionId}`
);

// MANUAL ASSOCIATION FOR BOB'S ACCOUNT
let associateBobTx = await new TokenAssociateTransaction()
    .setAccountId(bobId)
    .setTokenIds([tokenId])
    .freezeWith(client)
    .sign(bobKey);
let associateBobTxSubmit = await associateBobTx.execute(client);
let associateBobRx = await associateBobTxSubmit.getReceipt(client);
console.log(`\n- Bob NFT Manual Association: ${associateBobRx.status}`);
console.log(
`- See: https://hashscan.io/${network}/transaction/${associateBobTxSubmit.transactionId}`
);

Console output:

- Alice NFT auto-association: SUCCESS
- See: https://hashscan.io/testnet/transaction/0.0.4649505@1723230939.742284556

- Bob NFT manual association: SUCCESS
- See: https://hashscan.io/testnet/transaction/0.0.4649505@1723230942.397064432

NFT Transfer from Treasury to Alice

Now, let’s do the first NFT transfer and check the account balances before and after the send.

nft-part1.js
// BALANCE CHECK 1
oB = await bCheckerFcn(treasuryId);
aB = await bCheckerFcn(aliceId);
bB = await bCheckerFcn(bobId);
console.log(`\n- Treasury balance: ${oB[0]} NFTs of ID:${tokenId} and ${oB[1]}`);
console.log(`- Alice balance: ${aB[0]} NFTs of ID:${tokenId} and ${aB[1]}`);
console.log(`- Bob balance: ${bB[0]} NFTs of ID:${tokenId} and ${bB[1]}`);

// 1st TRANSFER NFT Treasury->Alice
let tokenTransferTx = await new TransferTransaction()
  .addNftTransfer(tokenId, 2, treasuryId, aliceId)
  .freezeWith(client)
  .sign(treasuryKey);
let tokenTransferSubmit = await tokenTransferTx.execute(client);
let tokenTransferRx = await tokenTransferSubmit.getReceipt(client);
console.log(`\n NFT transfer Treasury->Alice status: ${tokenTransferRx.status}`);
console.log(
`- See: https://hashscan.io/${network}/transaction/${tokenTransferSubmit.transactionId}`
);

// BALANCE CHECK 2: COPY/PASTE THE CODE ABOVE IN BALANCE CHECK 1
// BALANCE CHECK 2
oB = await bCheckerFcn(treasuryId);
aB = await bCheckerFcn(aliceId);
bB = await bCheckerFcn(bobId);
console.log(`\n- Treasury balance: ${oB[0]} NFTs of ID:${tokenId} and ${oB[1]}`);
console.log(`- Alice balance: ${aB[0]} NFTs of ID:${tokenId} and ${aB[1]}`);
console.log(`- Bob balance: ${bB[0]} NFTs of ID:${tokenId} and ${bB[1]}`);
nft-part1.js
// BALANCE CHECKER FUNCTION ==========================================
async function bCheckerFcn(id) {
  balanceCheckTx = await new AccountBalanceQuery()
    .setAccountId(id)
    .execute(client);
  return [
    balanceCheckTx.tokens._map.get(tokenId.toString()),
    balanceCheckTx.hbars,
  ];
}

Console output:

- Treasury balance: 4 NFTs of ID:0.0.4672119 and 1 ℏ
- Alice balance: undefined NFTs of ID:0.0.4672119 and 1 ℏ
- Bob balance: 0 NFTs of ID:0.0.4672119 and 1 ℏ

- NFT transfer Treasury -> Alice status: SUCCESS
- See: https://hashscan.io/testnet/transaction/0.0.4649505@1723230943.472094366

- Treasury balance: 3 NFTs of ID:0.0.4672119 and 1 ℏ
- Alice balance: 1 NFTs of ID:0.0.4672119 and 1 ℏ
- Bob balance: 0 NFTs of ID:0.0.4672119 and 1 ℏ

NFT Transfer from Alice to Bob

nft-part1.js
// 2nd NFT TRANSFER NFT Alice->Bob
let nftPrice = new Hbar(10000000, HbarUnit.Tinybar); // 1HBAR = 10,000,000 Tinybar
let tokenTransferTx2 = await new TransferTransaction()
	.addNftTransfer(tokenId, 2, aliceId, bobId)
	.addHbarTransfer(aliceId, nftPrice)
	.addHbarTransfer(bobId, nftPrice.negated())
	.freezeWith(client)
	.sign(aliceKey);
let tokenTransferTx2Sign = await tokenTransferTx2.sign(bobKey);
let tokenTransferSubmit2 = await tokenTransferTx2Sign.execute(client);
let tokenTransferRx2 = await tokenTransferSubmit2.getReceipt(client);
console.log(`\n NFT transfer Alice->Bob status: ${tokenTransferRx2.status}`);
console.log(
`- See: https://hashscan.io/${network}/transaction/${tokenTransferSubmit2.transactionId}`
);

// BALANCE CHECK 3: COPY/PASTE THE CODE ABOVE IN BALANCE CHECK 1

Console output:

- NFT transfer Alice -> Bob status: SUCCESS
- See: https://hashscan.io/testnet/transaction/0.0.4649505@1723230945.514991505

- Treasury balance: 3 NFTs of ID:0.0.4672119 and 1.01 ℏ
- Alice balance: 0 NFTs of ID:0.0.4672119 and 1.09 ℏ
- Bob balance: 1 NFTs of ID:0.0.4672119 and 0.9 ℏ

Remember from the documentation that royalty fees are paid from the fungible value exchanged, which was 100 HBAR in this case. The royalty fee is specified to be 50%, so that’s why the treasury collects 50 HBAR, and Alice collects the remaining 50 HBAR. Remember that when there’s no exchange of fungible value (like HBAR or a fungible token), the fallback fee is charged (currently 200 HBAR in our custom fee schedule).


Conclusion

Code Check ✅
nft-pt1.js
console.clear();
require("dotenv").config();

const {
  AccountId,
  PrivateKey,
  Client,
  TokenCreateTransaction,
  TokenInfoQuery,
  TokenType,
  CustomRoyaltyFee,
  CustomFixedFee,
  Hbar,
  HbarUnit,
  TokenSupplyType,
  TokenMintTransaction,
  TokenBurnTransaction,
  TransferTransaction,
  AccountBalanceQuery,
  AccountUpdateTransaction,
  TokenAssociateTransaction,
  TokenNftInfoQuery,
  NftId,
  AccountCreateTransaction,
} = require("@hashgraph/sdk");

// CONFIGURE ACCOUNTS AND CLIENT, AND GENERATE  accounts and client, and generate needed keys
const operatorId = AccountId.fromString(process.env.OPERATOR_ID);
const operatorKey = PrivateKey.fromStringECDSA(process.env.OPERATOR_KEY_HEX);
const network = process.env.NETWORK;

const client = Client.forNetwork(network).setOperator(operatorId, operatorKey);
client.setDefaultMaxTransactionFee(new Hbar(50));
client.setDefaultMaxQueryPayment(new Hbar(1));

async function main() {
  // CREATE NEW HEDERA ACCOUNTS TO REPRESENT OTHER USERS
  const initBalance = new Hbar(1);

  const treasuryKey = PrivateKey.generateECDSA();
  const [treasurySt, treasuryId] = await accountCreateFcn(
    treasuryKey,
    initBalance,
    client
  );
  console.log(
    `- Treasury's account: https://hashscan.io/testnet/account/${treasuryId}`
  );
  const aliceKey = PrivateKey.generateECDSA();
  const [aliceSt, aliceId] = await accountCreateFcn(
    aliceKey,
    initBalance,
    client
  );
  console.log(
    `- Alice's account: https://hashscan.io/testnet/account/${aliceId}`
  );
  const bobKey = PrivateKey.generateECDSA();
  const [bobSt, bobId] = await accountCreateFcn(bobKey, initBalance, client);
  console.log(`- Bob's account: https://hashscan.io/testnet/account/${bobId}`);

  // GENERATE KEYS TO MANAGE FUNCTIONAL ASPECTS OF THE TOKEN
  const supplyKey = PrivateKey.generateECDSA();
  const adminKey = PrivateKey.generateECDSA();
  const pauseKey = PrivateKey.generateECDSA();
  const freezeKey = PrivateKey.generateECDSA();
  const wipeKey = PrivateKey.generateECDSA();
  const kycKey = PrivateKey.generate();
  const newKycKey = PrivateKey.generate();

  // DEFINE CUSTOM FEE SCHEDULE
  let nftCustomFee = new CustomRoyaltyFee()
    .setNumerator(1)
    .setDenominator(10)
    .setFeeCollectorAccountId(treasuryId)
    .setFallbackFee(
      new CustomFixedFee().setHbarAmount(new Hbar(1, HbarUnit.Tinybar))
    ); // 1 HBAR = 100,000,000 Tinybar

  // IPFS CONTENT IDENTIFIERS FOR WHICH WE WILL CREATE NFTs - SEE uploadJsonToIpfs.js
  let CIDs = [
    Buffer.from(
      "ipfs://bafkreibr7cyxmy4iyckmlyzige4ywccyygomwrcn4ldcldacw3nxe3ikgq"
    ),
    Buffer.from(
      "ipfs://bafkreig73xgqp7wy7qvjwz33rp3nkxaxqlsb7v3id24poe2dath7pj5dhe"
    ),
    Buffer.from(
      "ipfs://bafkreigltq4oaoifxll3o2cc3e3q3ofqzu6puennmambpulxexo5sryc6e"
    ),
    Buffer.from(
      "ipfs://bafkreiaoswszev3uoukkepctzpnzw56ey6w3xscokvsvmfrqdzmyhas6fu"
    ),
    Buffer.from(
      "ipfs://bafkreih6cajqynaqwbrmiabk2jxpy56rpf25zvg5lbien73p5ysnpehyjm"
    ),
  ];

  // CREATE NFT WITH CUSTOM FEE
  let nftCreateTx = await new TokenCreateTransaction()
    .setTokenName("Fall Collection")
    .setTokenSymbol("LEAF")
    .setTokenType(TokenType.NonFungibleUnique)
    .setDecimals(0)
    .setInitialSupply(0)
    .setTreasuryAccountId(treasuryId)
    .setSupplyType(TokenSupplyType.Finite)
    .setMaxSupply(CIDs.length)
    .setCustomFees([nftCustomFee])
    .setAdminKey(adminKey.publicKey)
    .setSupplyKey(supplyKey.publicKey)
    .setPauseKey(pauseKey.publicKey)
    .setFreezeKey(freezeKey.publicKey)
    .setWipeKey(wipeKey.publicKey)
    .freezeWith(client)
    .sign(treasuryKey);

  let nftCreateTxSign = await nftCreateTx.sign(adminKey);
  let nftCreateSubmit = await nftCreateTxSign.execute(client);
  let nftCreateRx = await nftCreateSubmit.getReceipt(client);
  let tokenId = nftCreateRx.tokenId;
  console.log(`\n- Created NFT with Token ID: ${tokenId}`);
  console.log(
    `- See: https://hashscan.io/${network}/transaction/${nftCreateSubmit.transactionId}`
  );

  // TOKEN QUERY TO CHECK THAT THE CUSTOM FEE SCHEDULE IS ASSOCIATED WITH NFT
  var tokenInfo = await new TokenInfoQuery()
    .setTokenId(tokenId)
    .execute(client);
  console.log(` `);
  console.table(tokenInfo.customFees[0]);

  // MINT NEW BATCH OF NFTs - CAN MINT UP TO 10 NFT SERIALS IN A SINGLE TRANSACTION
  let [nftMintRx, mintTxId] = await tokenMinterFcn(CIDs);
  console.log(
    `\n- Mint ${CIDs.length} serials for NFT collection ${tokenId}: ${nftMintRx.status}`
  );
  console.log(`- See: https://hashscan.io/${network}/transaction/${mintTxId}`);

  // BURN THE LAST NFT IN THE COLLECTION
  let tokenBurnTx = await new TokenBurnTransaction()
    .setTokenId(tokenId)
    .setSerials([CIDs.length])
    .freezeWith(client)
    .sign(supplyKey);
  let tokenBurnSubmit = await tokenBurnTx.execute(client);
  let tokenBurnRx = await tokenBurnSubmit.getReceipt(client);
  console.log(`\n- Burn NFT with serial ${CIDs.length}: ${tokenBurnRx.status}`);
  console.log(
    `- See: https://hashscan.io/${network}/transaction/${tokenBurnSubmit.transactionId}`
  );

  var tokenInfo = await new TokenInfoQuery()
    .setTokenId(tokenId)
    .execute(client);
  console.log(`- Current NFT supply: ${tokenInfo.totalSupply}`);

  // AUTO-ASSOCIATION FOR ALICE'S ACCOUNT
  let associateTx = await new AccountUpdateTransaction()
    .setAccountId(aliceId)
    .setMaxAutomaticTokenAssociations(10)
    .freezeWith(client)
    .sign(aliceKey);
  let associateTxSubmit = await associateTx.execute(client);
  let associateRx = await associateTxSubmit.getReceipt(client);
  console.log(`\n- Alice NFT auto-association: ${associateRx.status}`);
  console.log(
    `- See: https://hashscan.io/${network}/transaction/${associateTxSubmit.transactionId}`
  );

  // MANUAL ASSOCIATION FOR BOB'S ACCOUNT
  let associateBobTx = await new TokenAssociateTransaction()
    .setAccountId(bobId)
    .setTokenIds([tokenId])
    .freezeWith(client)
    .sign(bobKey);
  let associateBobTxSubmit = await associateBobTx.execute(client);
  let associateBobRx = await associateBobTxSubmit.getReceipt(client);
  console.log(`\n- Bob NFT manual association: ${associateBobRx.status}`);
  console.log(
    `- See: https://hashscan.io/${network}/transaction/${associateBobTxSubmit.transactionId}`
  );

  // BALANCE CHECK 1
  oB = await bCheckerFcn(treasuryId);
  aB = await bCheckerFcn(aliceId);
  bB = await bCheckerFcn(bobId);
  console.log(
    `\n- Treasury balance: ${oB[0]} NFTs of ID:${tokenId} and ${oB[1]}`
  );
  console.log(`- Alice balance: ${aB[0]} NFTs of ID:${tokenId} and ${aB[1]}`);
  console.log(`- Bob balance: ${bB[0]} NFTs of ID:${tokenId} and ${bB[1]}`);

  // 1st TRANSFER NFT Treasury -> Alice
  let tokenTransferTx = await new TransferTransaction()
    .addNftTransfer(tokenId, 2, treasuryId, aliceId)
    .freezeWith(client)
    .sign(treasuryKey);
  let tokenTransferSubmit = await tokenTransferTx.execute(client);
  let tokenTransferRx = await tokenTransferSubmit.getReceipt(client);
  console.log(
    `\n- NFT transfer Treasury -> Alice status: ${tokenTransferRx.status}`
  );
  console.log(
    `- See: https://hashscan.io/${network}/transaction/${tokenTransferSubmit.transactionId}`
  );

  // BALANCE CHECK 2
  oB = await bCheckerFcn(treasuryId);
  aB = await bCheckerFcn(aliceId);
  bB = await bCheckerFcn(bobId);
  console.log(
    `\n- Treasury balance: ${oB[0]} NFTs of ID:${tokenId} and ${oB[1]}`
  );
  console.log(`- Alice balance: ${aB[0]} NFTs of ID:${tokenId} and ${aB[1]}`);
  console.log(`- Bob balance: ${bB[0]} NFTs of ID:${tokenId} and ${bB[1]}`);

  // 2nd NFT TRANSFER NFT Alice -> Bob
  let nftPrice = new Hbar(10000000, HbarUnit.Tinybar); // 1 HBAR = 10,000,000 Tinybar

  let tokenTransferTx2 = await new TransferTransaction()
    .addNftTransfer(tokenId, 2, aliceId, bobId)
    .addHbarTransfer(aliceId, nftPrice)
    .addHbarTransfer(bobId, nftPrice.negated())
    .freezeWith(client)
    .sign(aliceKey);
  let tokenTransferTx2Sign = await tokenTransferTx2.sign(bobKey);
  let tokenTransferSubmit2 = await tokenTransferTx2Sign.execute(client);
  let tokenTransferRx2 = await tokenTransferSubmit2.getReceipt(client);
  console.log(
    `\n- NFT transfer Alice -> Bob status: ${tokenTransferRx2.status}`
  );
  console.log(
    `- See: https://hashscan.io/${network}/transaction/${tokenTransferSubmit2.transactionId}`
  );

  // BALANCE CHECK 3
  oB = await bCheckerFcn(treasuryId);
  aB = await bCheckerFcn(aliceId);
  bB = await bCheckerFcn(bobId);
  console.log(
    `\n- Treasury balance: ${oB[0]} NFTs of ID:${tokenId} and ${oB[1]}`
  );
  console.log(`- Alice balance: ${aB[0]} NFTs of ID:${tokenId} and ${aB[1]}`);
  console.log(`- Bob balance: ${bB[0]} NFTs of ID:${tokenId} and ${bB[1]}`);

  console.log(
    `\n- THE END ============================================================\n`
  );
  console.log(`- 👇 Go to:`);
  console.log(`- 🔗 www.hedera.com/discord\n`);

  client.close();

  // ACCOUNT CREATOR FUNCTION ==========================================
  async function accountCreateFcn(pvKey, iBal, client) {
    const response = await new AccountCreateTransaction()
      .setInitialBalance(iBal)
      .setKey(pvKey.publicKey)
      .setMaxAutomaticTokenAssociations(10)
      .execute(client);
    const receipt = await response.getReceipt(client);
    return [receipt.status, receipt.accountId];
  }

  // TOKEN MINTER FUNCTION ==========================================
  async function tokenMinterFcn(CIDs) {
    let mintTx = new TokenMintTransaction()
      .setTokenId(tokenId)
      .setMetadata(CIDs)
      .freezeWith(client);
    let mintTxSign = await mintTx.sign(supplyKey);
    let mintTxSubmit = await mintTxSign.execute(client);
    let mintRx = await mintTxSubmit.getReceipt(client);
    return [mintRx, mintTxSubmit.transactionId];
  }

  // BALANCE CHECKER FUNCTION ==========================================
  async function bCheckerFcn(id) {
    balanceCheckTx = await new AccountBalanceQuery()
      .setAccountId(id)
      .execute(client);
    return [
      balanceCheckTx.tokens._map.get(tokenId.toString()),
      balanceCheckTx.hbars,
    ];
  }
}

main();

Additional Resources

Get a .

Set up your environment .

✅ If you want the entire code used for this tutorial, skip to the section below.

Note: While the following examples are in JavaScript, official SDKs supporting and are also available and implemented very similarly alongside community-supported SDKs in and various other frameworks or languages.

Let's create additional Hedera accounts to represent users for this scenario, such as the Treasury, Alice, and Bob. These accounts are created using your Testnet account credentials from the Hedera portal (see the resources for ). Account creation starts by generating a private key for the new account and then calling a reusable function (accountCreatorFcn) that uses the new key, an initial balance, and the Hedera client. You can easily reuse this function if you need to create more accounts in the future.

Let’s start by defining the custom fees for the NFT. are distributed to the specified accounts each time the token is transferred. Depending on the token type (fungible or non-fungible), you can specify a custom fee to be fixed, fractional, or a royalty. An NFT can only have fixed or royalty fees, so in this example, we’ll go with a royalty fee. This enables collecting a fraction of the value exchanged for the NFT when ownership is transferred from one person to another.

The images and their metadata live in the InterPlanetary File System (IPFS), which provides decentralized storage. In the next section, we will need the metadata to mint NFTs. For the metadata, we use the standard in .

Now, let’s . 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 default values are used if they’re not specified. 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; for more info on the defaults check out the documentation.

In the code above for the NFT creation, the decimals and initial supply must be set to zero. Once the token is created, you will have to mint each NFT using the token mint operation. Specifying a supply key during token creation is a requirement to be able to and tokens.

This association between an account and a token ID can be done in two ways, or automatically. Note that automatic associations can be done for both and accounts. For the purposes of our example, we’ll do both.

Finally, let’s do two of the NFT with serial number 2 and see how the royalty fees are collected. The first transfer will be from the Treasury to Alice, and the second NFT transfer will be from Alice to Bob in exchange for 100 HBAR.

As you remember from the , the treasury account and any fee-collecting accounts for a token are exempt from paying custom transaction fees when the token is transferred. Since the treasury account is also the fee collector for the token, that means there are no royalty fees collected in this first transfer.

You just learned how to create an NFT on the Hedera network at the native layer without the need to code complex smart contracts! You can create, mint, burn, associate, and transfer NFTs with just a few lines of code in your favorite programming language. Continue to to learn how to work with compliance features like , update tokens, and schedule transactions. Then in, you will see how to pause, freeze, wipe, and delete tokens.

➡

➡ Have a question? Ask on

Hedera testnet account
here
Go
Java
.NET
getting started
Custom fees
this specification
create the token
mint
burn
manually
existing
newly created
transfers
Custom Token Fees documentation
Project Repository
StackOverflow
Code Check
blog post

Writer: Ed, DevRel Engineer

Editor: Krystal, Technical Writer

Part 2
Part 3

|

|

GitHub
LinkedIn
GitHub
Twitter
Know your Customer (KYC)