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:
Let's start!
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.
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.
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.
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.
In this section, you'll learn about seven test cases related to setting token keys for NFTs.
Output: The token becomes immutable when you don't set an admin key. This means that none of the token properties can be updated.
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
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.
let tokenUpdateTx = await new TokenUpdateTransaction() .setTokenId(tokenId) .setSupplyKey(null) // if you set this to null, nothing happens .freezeWith(client) .sign(adminKey);
Code example: all-keys-remove-supply.js
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.
let tokenUpdateTx = await new TokenUpdateTransaction() .setTokenId(tokenId) .setSupplyKey(newSupplyKey) .freezeWith(client) .sign(adminKey);
Code example: all-keys-update-supply.js
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.
let tokenUpdateTx = await new TokenUpdateTransaction() .setTokenId(tokenId) .setAdminKey(newAdminKey) .freezeWith(client) .sign(newAdminKey); let tokenUpdateTxSign = await tokenUpdateTx.sign(adminKey);
Code example: token-update-admin-key.js
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.
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
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.
// 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.
// 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
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".
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
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.
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.
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" } */
❓ 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.