Hedera Token Service: NFT Token Keys Edge Cases
Small profile pic michiel dec 2022
Feb 07, 2023
by Michiel Mulders
Developer Advocate at Swirlds Labs

This technical blog post tries to answer all your questions about token keys, how to use them, and their edge cases.

Token keys can be set for any token created using the Hedera Token Service (HTS). This means you can set token keys for both fungible and non-fungible tokens. In this blog post, we'll focus on non-fungible tokens.

Here’s an overview of what you’ll learn in this blog post:

  • Which keys can you set for tokens?
  • Why do you set token keys?
  • How to create an NFT using the JavaScript SDK?
  • Token keys test cases
    • Case 1: Can you make changes to an NFT when you don't set an admin key?
    • Case 2: Can the admin key remove other keys?
    • Case 3: Can the admin key update other keys?
    • Case 4: Can the admin key remove itself?
    • Case 5: Can one account ID be set to different keys for the same token?
    • Case 6: Can you assign multiple accounts to a single key?
    • Case 7: Can you set no keys for an NFT?
  • Which keys are considered high-risk for collectible NFTs?

Let's start!

Which keys can you set for tokens?

Here’s an overview of the seven possible keys you can set for a token.

  • Admin key: This key can perform token update and token delete operations on the token. The admin key has the authority to change the supply key, freeze key, pause key, wipe key, and KYC key. It can also update the treasury account of the token. If empty, the token can be perceived as immutable (not able to be updated/deleted).

  • Freeze key: This key is used to freeze or unfreeze token accounts. When an account is frozen, it cannot perform any transactions.

  • KYC key: This key is used to manage the token's KYC (Know Your Customer) information. It can be used to add, update, or remove KYC information for token accounts.

  • Wipe key: This key is used to wipe the balance of a token account. This can be useful in cases where the account owner has lost access to the account or it has been compromised.

  • Supply key: This key is used to manage the total supply of a token. It can be used to mint new tokens or burn existing ones. If the supply key is not set, it’s not possible to mint or burn tokens.

  • Pause key: This key has the authority to pause or unpause a token. Pausing a token prevents the token from participating in all transactions.

  • Fee Schedule key: This key can change the token's custom fee schedule. It must sign a TokenFeeScheduleUpdate transaction. A token without a fee schedule key is immutable, which means you can’t set a custom fee schedule after the token has been created.

Why do you set token keys?

When creating a token on the Hedera network, setting keys serves several important purposes.

  • Security

Setting different keys for different functions ensures that only authorized individuals or entities have control over specific actions related to the token. For example, only the admin key holder can update the token's properties, only the treasury key holder can transfer funds from the token's treasury account, etc.

  • Flexibility

Setting multiple keys for the same function allows for multiple individuals or entities to perform that function, providing redundancy and fail-safes.

  • Transparency

Setting different keys for different functions also provides transparency and accountability to the token holders and other stakeholders. They can see who has the power to perform certain actions and can hold them accountable for their actions.

  • Compliance

Setting different keys for different functions also allows for compliance with regulatory requirements. For example, setting a KYC key allows for the token issuer to comply with know-your-customer regulations and setting a freeze key allows for freezing of accounts that are suspected of engaging in illegal activities.


How to create an NFT using the JavaScript SDK?

Here's an example of how you can create an NFT with the Hedara Token Service using the Hedera JavaScript SDK, setting all token keys.

Code Snippet Background
let nftCreate = await new TokenCreateTransaction()
    .setTokenName("Fall Collection")
    .setTokenSymbol("LEAF")
    .setTokenType(TokenType.NonFungibleUnique)
    .setDecimals(0)
    .setInitialSupply(0)
    .setTreasuryAccountId(treasuryId) // needs to sign
    .setSupplyType(TokenSupplyType.Finite)
    .setMaxSupply(5)
    // Set keys
    .setAdminKey(adminKey)
    .setFreezeKey(randomKey)
    .setKycKey(randomKey)
    .setWipeKey(randomKey)
    .setSupplyKey(randomKey)
    .setPauseKey(randomKey)
    .setFeeScheduleKey(randomKey)
    .freezeWith(client);

Now, let's take a look at specific test cases related to setting token keys.

Token keys test cases

In this section, you'll learn about seven test cases related to setting token keys for NFTs.

Case 1: Can you make changes to an NFT when you don't set an admin key?

Output: The token becomes immutable when you don't set an admin key. This means that none of the token properties can be updated.

Code Snippet Background
let nftCreateTx = await new TokenCreateTransaction()
    .setTokenName("Fall Collection")
    .setTokenSymbol("LEAF")
    .setTokenType(TokenType.NonFungibleUnique)
    .setDecimals(0)
    .setInitialSupply(0)
    .setTreasuryAccountId(treasuryId)
    .setSupplyType(TokenSupplyType.Finite)
    .setMaxSupply(5)
    // No admin key, only the required supply key is set
    .setSupplyKey(treasuryKey)
    .freezeWith(client)
    .sign(treasuryKey);

Code example: immutable-token.js

Case 2: Can the admin key remove other keys or itself?

Output: No, it's not possible to remove keys or itself. The admin key is only allowed to update keys. When you set a key to "null" or "undefined", nothing will change.

Code Snippet Background
let tokenUpdateTx = await new TokenUpdateTransaction()
    .setTokenId(tokenId)
    .setSupplyKey(null) // if you set this to null, nothing happens
    .freezeWith(client)
    .sign(adminKey);

Case 3: Can the admin key update other keys?

Output: Yes, the admin key has the authority to change the supply key, freeze key, pause key, wipe key, and KYC key. Note that the updated key doesn't need to sign the transaction for it to be assigned to a specific key for the token.

In the example below, the supply key is updated with a new supply key. The "newSupplyKey" doesn't need to sign the transaction. Only the "adminKey" needs to sign.

Code Snippet Background
let tokenUpdateTx = await new TokenUpdateTransaction()
    .setTokenId(tokenId)
    .setSupplyKey(newSupplyKey) 
    .freezeWith(client)
    .sign(adminKey);

Case 4: Can the admin key be updated to a new admin key?

Output: Yes, the admin key can be updated when it's set. Both the old and the new admin key must sign the transaction to be successful.

Code Snippet Background
let tokenUpdateTx = await new TokenUpdateTransaction()
    .setTokenId(tokenId)
    .setAdminKey(newAdminKey)
    .freezeWith(client)
    .sign(newAdminKey);

let tokenUpdateTxSign = await tokenUpdateTx.sign(adminKey);

Case 5: Can one account ID be set to different keys for the same token?

Output: Yes, you are allowed to set the same account for multiple keys on a token. For instance, our base example uses the "random account ID" for 6 keys.

Code Snippet Background
let nftCreate = await new TokenCreateTransaction()
    .setTokenName("Fall Collection")
    .setTokenSymbol("LEAF")
    .setTokenType(TokenType.NonFungibleUnique)
    .setDecimals(0)
    .setInitialSupply(0)
    .setTreasuryAccountId(treasuryId)
    .setSupplyType(TokenSupplyType.Finite)
    .setMaxSupply(5)
    // Set keys
    .setAdminKey(adminKey)
    .setFreezeKey(randomKey) // 1
    .setKycKey(randomKey) // 2
    .setWipeKey(randomKey) // 3
    .setSupplyKey(randomKey) // 4
    .setPauseKey(randomKey) // 5
    .setFeeScheduleKey(randomKey) // 6
    .freezeWith(client);

Code example: all-keys.js

Case 6: Can you assign multiple accounts to a single key?

Output: Yes, you can create a KeyList, which acts as a multisig. For instance, you can create a KeyList that contains two accounts and set the signing requirements to 2-out-of-2. Both accounts need to sign when the specific key is required.

Below you will find an example with an admin key that has been assigned a 2-out-of-2 KeyList.

Code Snippet Background
// Create keylist
console.log(`- Generating keylist...`);
const keyList = new KeyList([key1.publicKey, key2.publicKey], 2); // 2-out-of-2

// Create NFT
console.log(`\n- Creating NFT`);
let nftCreate = await new TokenCreateTransaction()
    .setNodeAccountIds(nodeId)
    .setTokenName("Fall Collection")
    .setTokenSymbol("LEAF")
    .setTokenType(TokenType.NonFungibleUnique)
    .setDecimals(0)
    .setInitialSupply(0)
    .setTreasuryAccountId(treasuryId) // needs to sign
    .setSupplyType(TokenSupplyType.Finite)
    .setMaxSupply(5)
    // Set keys
    .setAdminKey(keyList) // multisig (keylist)
    .setSupplyKey(randomKey)
    .freezeWith(client)
    .sign(treasuryKey);

When you want to execute a transaction, both keys need to sign.

Code Snippet Background
// Adding multisig signatures
const sig1 = key1.signTransaction(nftCreate);
const sig2 = key2.signTransaction(nftCreate);
const nftCreateTxSign = nftCreate.addSignature(key1.publicKey, sig1).addSignature(key2.publicKey, sig2);

let nftCreateSubmit = await nftCreateTxSign.execute(client);
let nftCreateRx = await nftCreateSubmit.getReceipt(client);
let tokenId = nftCreateRx.tokenId;
console.log(`- Created NFT with Token ID: ${tokenId}`);

Code example: token-admin-keylist.js

Case 7: Can you set no keys for an NFT?

Output: No, the supply key is the only required key when you don't set any other keys for an NFT. If you don't set a supply key, you get the error "TOKEN_HAS_NO_SUPPLY_KEY".

Code Snippet Background
let nftCreate = await new TokenCreateTransaction()
    .setTokenName("Fall Collection")
    .setTokenSymbol("LEAF")
    .setTokenType(TokenType.NonFungibleUnique)
    .setDecimals(0)
    .setInitialSupply(0)
    .setTreasuryAccountId(treasuryId) // needs to sign
    .setSupplyType(TokenSupplyType.Finite)
    .setMaxSupply(5)
    // No keys throws error: TOKEN_HAS_NO_SUPPLY_KEY
    .setSupplyKey(randomKey) // REQUIRED
    .freezeWith(client);

Code example: no-keys.js

Which keys are considered high-risk for collectible NFTs?

This blog post section explores the different risk scores associated with setting specific keys for non-fungible tokens. Each key receives a relative score and a risk level based on the actions it can carry out. The below risk information is only applicable to NFT collections. 

This section aims to educate NFT creators on which keys to set for their NFT collection and to help them understand the implications.

Here's an overview of all keys, their risk score, and why they got this score.

  • Admin key [score: 200 - HIGH]: This key can update token properties and change other keys for the NFT. Therefore, it could create a new account and update a specific key for the NFT to the newly created account it owns. Besides that, the admin key can delete an NFT forever. 
  • Wipe key [score: 200 - HIGH]: The wipe key has received an equal risk score of 200 because it can wipe the NFT balance of an account. This key is often set as a backup mechanism when a user loses access to their account, or it's corrupted. The wipe key can remove the account balance of the affected account, and the user can then make a new account and receive the token again. However, this key can also wipe the account balance of users that didn't request this.
  • Freeze key [score: 50 - MEDIUM]: When the freeze key freezes an account, this account can't make any transactions using the specific NFT. In short, the freeze key can exclude someone from using the NFT.
  • KYC key [score: 50 - MEDIUM]: The KYC key can grant or revoke KYC of an account for the NFT's transactions when set. When the KYC for an account is revoked, it's impossible to use the NFT in transactions.
  • Pause key [score: 50 - MEDIUM]: The pause key can pause the entire NFT collection. It means that nobody can use any NFT in transactions. 
  • Fee schedule key [score: 40 - LOW]: The fee schedule key can change the fee users pay when they trade an NFT. The key only received a low risk score because the maximum fee that can be set is 100%. So, a user trading an NFT for 10 Hbar will pay an additional 10 Hbar fee to the treasury account. It's not a big exploit, but one to consider.
  • Supply key: [score: 20 - LOW]: When the supply type is set to "INFINITE", the supply key can mint as many NFTs for a collection as they want. This means the supply key can dilute an NFT project. The risk is eliminated when the supply type is set to FINITE because there's a fixed limit for the number of NFTs this key can mint.

Note: When someone buys an NFT from an NFT collection, they want to ensure that nobody can tamper with the NFT itself. Therefore, most projects set no keys, making the NFT immutable. This results in a zero risk score. 

If you want to automate the process of calculating risk levels for NFTs, you can use the Hedera NFT Utilities SDK to automate this task. The risk score calculation section shows you how to use this functionality and what output you can expect. Here's a quick example.

Code Snippet Background
const { calculateRiskScoreFromData } = require('@hashgraph/nft-utilities');

const tokenInformation = {
        "admin_key": null,
        "auto_renew_account": "0.0.784037", "auto_renew_period": 7776000,
        "freeze_key": null,
        ...
}

const results = calculateRiskScoreFromData(tokenInformation);

/* Output:
 * { "riskScore": "0", "riskLevel": "NORISK" }
 */

Learn more about token keys?

❓ Reach out on Discord or look at the docs about token creation using the Hedera Token Service.

📚 If you want to explore all examples, check the token keys example repository.