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 blog post. 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.
Get a Hedera testnet account.
Set up your environment here.
✅ If you want the entire code used for this tutorial, skip to the Code Check section below.
Note: While the following examples are in JavaScript, official SDKs supporting Go and Java are also available and implemented very similarly alongside community-supported SDKs in .NET and various other frameworks or languages.
Create New Hedera Accounts and Generate Keys for the NFT
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 getting started). 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.
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 USERSconstinitBalance=newHbar(200);consttreasuryKey=PrivateKey.generateECDSA();const [treasurySt,treasuryId] =awaitaccountCreateFcn( treasuryKey, initBalance, client);console.log(`- Treasury's account: https://hashscan.io/testnet/account/${treasuryId}`);constaliceKey=PrivateKey.generateECDSA();const [aliceSt,aliceId] =awaitaccountCreateFcn(aliceKey, initBalance, client);console.log(`- Alice's account: https://hashscan.io/testnet/account/${aliceId}`);constbobKey=PrivateKey.generateECDSA();const [bobSt,bobId] =awaitaccountCreateFcn(bobKey, initBalance, client);console.log(`- Bob's account: https://hashscan.io/testnet/account/${bobId}`);// GENERATE KEYS TO MANAGE FUNCTIONAL ASPECTS OF THE TOKENconstsupplyKey=PrivateKey.generate();constadminKey=PrivateKey.generate();constpauseKey=PrivateKey.generate();constfreezeKey=PrivateKey.generate();constwipeKey=PrivateKey.generate();constkycKey=PrivateKey.generate();constnewKycKey=PrivateKey.generate();
nft-part1.js
// ACCOUNT CREATOR FUNCTION ==========================================asyncfunctionaccountCreateFcn(pvKey, iBal, client) {constresponse=awaitnewAccountCreateTransaction().setInitialBalance(iBal).setKey(pvKey.publicKey).setMaxAutomaticTokenAssociations(10).execute(client);constreceipt=awaitresponse.getReceipt(client);return [receipt.status,receipt.accountId];}
Let’s start by defining the custom fees for the NFT. Custom fees 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 this specification.
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.jslet CIDs = [Buffer.from("ipfs://bafkreibr7cyxmy4iyckmlyzige4ywccyygomwrcn4ldcldacw3nxe3ikgq"),Buffer.from("ipfs://bafkreig73xgqp7wy7qvjwz33rp3nkxaxqlsb7v3id24poe2dath7pj5dhe"),Buffer.from("ipfs://bafkreigltq4oaoifxll3o2cc3e3q3ofqzu6puennmambpulxexo5sryc6e"),Buffer.from("ipfs://bafkreiaoswszev3uoukkepctzpnzw56ey6w3xscokvsvmfrqdzmyhas6fu"),Buffer.from("ipfs://bafkreih6cajqynaqwbrmiabk2jxpy56rpf25zvg5lbien73p5ysnpehyjm"),];
Now, let’s create the token. 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.
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 FEElet nftCreateTx =awaitnewTokenCreateTransaction().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 =awaitnftCreateTx.sign(adminKey)let nftCreateSubmit =awaitnftCreateTxSign.execute(client);let nftCreateRx =awaitnftCreateSubmit.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 NFTvar tokenInfo =awaitnewTokenInfoQuery().setTokenId(tokenId).execute(client);console.log(` `);console.table(tokenInfo.customFees[0]);
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 mint and burn tokens.
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 TRANSACTIONlet [nftMintRx, mintTxId] =awaittokenMinterFcn(CIDs);console.log(`\n- Mint ${CIDs.length} serials for NFT collection ${tokenId}: ${nftMintRx.status}`);console.log(`- See: https://hashscan.io/${network}/transaction/${mintTxId}`);
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 COLLECTIONlet tokenBurnTx =awaitnewTokenBurnTransaction().setTokenId(tokenId).setSerials([CIDs.length]).freezeWith(client).sign(supplyKey);let tokenBurnSubmit =awaittokenBurnTx.execute(client);let tokenBurnRx =awaittokenBurnSubmit.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 =awaitnewTokenInfoQuery().setTokenId(tokenId).execute(client);console.log(`\n- Current NFT supply: ${tokenInfo.totalSupply}`);
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.
This association between an account and a token ID can be done in two ways, manually or automatically. Note that automatic associations can be done for both existing and newly created accounts. For the purposes of our example, we’ll do both.
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 ACCOUNTlet associateTx =awaitnewAccountUpdateTransaction().setAccountId(aliceId).setMaxAutomaticTokenAssociations(10).freezeWith(client).sign(aliceKey);let associateTxSubmit =awaitassociateTx.execute(client);let associateRx =awaitassociateTxSubmit.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 ACCOUNTlet associateBobTx =awaitnewTokenAssociateTransaction().setAccountId(bobId).setTokenIds([tokenId]).freezeWith(client).sign(bobKey);let associateBobTxSubmit =awaitassociateBobTx.execute(client);let associateBobRx =awaitassociateBobTxSubmit.getReceipt(client);console.log(`\n- Bob NFT Manual Association: ${associateBobRx.status}`);console.log(`- See: https://hashscan.io/${network}/transaction/${associateBobTxSubmit.transactionId}`);
Finally, let’s do two transfers 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.
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 1oB =awaitbCheckerFcn(treasuryId);aB =awaitbCheckerFcn(aliceId);bB =awaitbCheckerFcn(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->Alicelet tokenTransferTx =awaitnewTransferTransaction().addNftTransfer(tokenId,2, treasuryId, aliceId).freezeWith(client).sign(treasuryKey);let tokenTransferSubmit =awaittokenTransferTx.execute(client);let tokenTransferRx =awaittokenTransferSubmit.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 2oB =awaitbCheckerFcn(treasuryId);aB =awaitbCheckerFcn(aliceId);bB =awaitbCheckerFcn(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 ==========================================asyncfunctionbCheckerFcn(id) { balanceCheckTx =awaitnewAccountBalanceQuery().setAccountId(id).execute(client);return [balanceCheckTx.tokens._map.get(tokenId.toString()),balanceCheckTx.hbars, ];}
As you remember from the Custom Token Fees documentation, 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.
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
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 Part 2 to learn how to work with compliance features like Know your Customer (KYC), update tokens, and schedule transactions. Then in Part 3, you will see how to pause, freeze, wipe, and delete tokens.
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 keysconstoperatorId=AccountId.fromString(process.env.OPERATOR_ID);constoperatorKey=PrivateKey.fromStringECDSA(process.env.OPERATOR_KEY_HEX);constnetwork=process.env.NETWORK;constclient=Client.forNetwork(network).setOperator(operatorId, operatorKey);client.setDefaultMaxTransactionFee(newHbar(50));client.setDefaultMaxQueryPayment(newHbar(1));asyncfunctionmain() {// CREATE NEW HEDERA ACCOUNTS TO REPRESENT OTHER USERSconstinitBalance=newHbar(1);consttreasuryKey=PrivateKey.generateECDSA();const [treasurySt,treasuryId] =awaitaccountCreateFcn( treasuryKey, initBalance, client );console.log(`- Treasury's account: https://hashscan.io/testnet/account/${treasuryId}` );constaliceKey=PrivateKey.generateECDSA();const [aliceSt,aliceId] =awaitaccountCreateFcn( aliceKey, initBalance, client );console.log(`- Alice's account: https://hashscan.io/testnet/account/${aliceId}` );constbobKey=PrivateKey.generateECDSA();const [bobSt,bobId] =awaitaccountCreateFcn(bobKey, initBalance, client);console.log(`- Bob's account: https://hashscan.io/testnet/account/${bobId}`);// GENERATE KEYS TO MANAGE FUNCTIONAL ASPECTS OF THE TOKENconstsupplyKey=PrivateKey.generateECDSA();constadminKey=PrivateKey.generateECDSA();constpauseKey=PrivateKey.generateECDSA();constfreezeKey=PrivateKey.generateECDSA();constwipeKey=PrivateKey.generateECDSA();constkycKey=PrivateKey.generate();constnewKycKey=PrivateKey.generate();// DEFINE CUSTOM FEE SCHEDULElet nftCustomFee =newCustomRoyaltyFee().setNumerator(1).setDenominator(10).setFeeCollectorAccountId(treasuryId).setFallbackFee(newCustomFixedFee().setHbarAmount(newHbar(1,HbarUnit.Tinybar)) ); // 1 HBAR = 100,000,000 Tinybar// IPFS CONTENT IDENTIFIERS FOR WHICH WE WILL CREATE NFTs - SEE uploadJsonToIpfs.jslet 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 FEElet nftCreateTx =awaitnewTokenCreateTransaction().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 =awaitnftCreateTx.sign(adminKey);let nftCreateSubmit =awaitnftCreateTxSign.execute(client);let nftCreateRx =awaitnftCreateSubmit.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 NFTvar tokenInfo =awaitnewTokenInfoQuery().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 TRANSACTIONlet [nftMintRx, mintTxId] =awaittokenMinterFcn(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 COLLECTIONlet tokenBurnTx =awaitnewTokenBurnTransaction().setTokenId(tokenId).setSerials([CIDs.length]).freezeWith(client).sign(supplyKey);let tokenBurnSubmit =awaittokenBurnTx.execute(client);let tokenBurnRx =awaittokenBurnSubmit.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 =awaitnewTokenInfoQuery().setTokenId(tokenId).execute(client);console.log(`- Current NFT supply: ${tokenInfo.totalSupply}`);// AUTO-ASSOCIATION FOR ALICE'S ACCOUNTlet associateTx =awaitnewAccountUpdateTransaction().setAccountId(aliceId).setMaxAutomaticTokenAssociations(10).freezeWith(client).sign(aliceKey);let associateTxSubmit =awaitassociateTx.execute(client);let associateRx =awaitassociateTxSubmit.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 ACCOUNTlet associateBobTx =awaitnewTokenAssociateTransaction().setAccountId(bobId).setTokenIds([tokenId]).freezeWith(client).sign(bobKey);let associateBobTxSubmit =awaitassociateBobTx.execute(client);let associateBobRx =awaitassociateBobTxSubmit.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 =awaitbCheckerFcn(treasuryId); aB =awaitbCheckerFcn(aliceId); bB =awaitbCheckerFcn(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 -> Alicelet tokenTransferTx =awaitnewTransferTransaction().addNftTransfer(tokenId,2, treasuryId, aliceId).freezeWith(client).sign(treasuryKey);let tokenTransferSubmit =awaittokenTransferTx.execute(client);let tokenTransferRx =awaittokenTransferSubmit.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 =awaitbCheckerFcn(treasuryId); aB =awaitbCheckerFcn(aliceId); bB =awaitbCheckerFcn(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 -> Boblet nftPrice =newHbar(10000000,HbarUnit.Tinybar); // 1 HBAR = 10,000,000 Tinybarlet tokenTransferTx2 =awaitnewTransferTransaction().addNftTransfer(tokenId,2, aliceId, bobId).addHbarTransfer(aliceId, nftPrice).addHbarTransfer(bobId,nftPrice.negated()).freezeWith(client).sign(aliceKey);let tokenTransferTx2Sign =awaittokenTransferTx2.sign(bobKey);let tokenTransferSubmit2 =awaittokenTransferTx2Sign.execute(client);let tokenTransferRx2 =awaittokenTransferSubmit2.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 =awaitbCheckerFcn(treasuryId); aB =awaitbCheckerFcn(aliceId); bB =awaitbCheckerFcn(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 ==========================================asyncfunctionaccountCreateFcn(pvKey, iBal, client) {constresponse=awaitnewAccountCreateTransaction().setInitialBalance(iBal).setKey(pvKey.publicKey).setMaxAutomaticTokenAssociations(10).execute(client);constreceipt=awaitresponse.getReceipt(client);return [receipt.status,receipt.accountId]; }// TOKEN MINTER FUNCTION ==========================================asyncfunctiontokenMinterFcn(CIDs) {let mintTx =newTokenMintTransaction().setTokenId(tokenId).setMetadata(CIDs).freezeWith(client);let mintTxSign =awaitmintTx.sign(supplyKey);let mintTxSubmit =awaitmintTxSign.execute(client);let mintRx =awaitmintTxSubmit.getReceipt(client);return [mintRx,mintTxSubmit.transactionId]; }// BALANCE CHECKER FUNCTION ==========================================asyncfunctionbCheckerFcn(id) { balanceCheckTx =awaitnewAccountBalanceQuery().setAccountId(id).execute(client);return [balanceCheckTx.tokens._map.get(tokenId.toString()),balanceCheckTx.hbars, ]; }}main();