hedera-token-service
Compare original and translation side by side
🇺🇸
Original
English🇨🇳
Translation
ChineseHedera Token Service (HTS) — JavaScript SDK
Hedera Token Service (HTS) — JavaScript SDK
HTS is Hedera's native token engine. It lets you create and manage fungible tokens and NFTs without writing smart contracts. Tokens created through HTS are first-class network entities with built-in compliance controls (KYC, freeze, wipe, pause) and custom fee schedules.
HTS是Hedera的原生代币引擎,无需编写智能合约即可创建和管理可替代代币与NFTs。通过HTS创建的代币是一等网络实体,具备内置合规控制(KYC、冻结、清零、暂停)和自定义费用方案。
Setup
环境搭建
All imports come from . Two setup patterns exist:
@hiero-ledger/sdk所有导入均来自,有两种搭建模式:
@hiero-ledger/sdkClient + setOperator (direct)
Client + setOperator(直接模式)
js
import { Client, AccountId, PrivateKey } from "@hiero-ledger/sdk";
const client = Client.forName(process.env.HEDERA_NETWORK)
.setOperator(
AccountId.fromString(process.env.OPERATOR_ID),
PrivateKey.fromStringECDSA(process.env.OPERATOR_KEY),
);js
import { Client, AccountId, PrivateKey } from "@hiero-ledger/sdk";
const client = Client.forName(process.env.HEDERA_NETWORK)
.setOperator(
AccountId.fromString(process.env.OPERATOR_ID),
PrivateKey.fromStringECDSA(process.env.OPERATOR_KEY),
);Wallet + LocalProvider (signer-based)
Wallet + LocalProvider(签名者模式)
js
import { Wallet, LocalProvider } from "@hiero-ledger/sdk";
const provider = new LocalProvider();
const wallet = new Wallet(process.env.OPERATOR_ID, process.env.OPERATOR_KEY, provider);With the signer pattern, use , , , and instead of / .
freezeWithSigner(wallet)signWithSigner(wallet)executeWithSigner(wallet)getReceiptWithSigner(wallet)freezeWith(client)execute(client)js
import { Wallet, LocalProvider } from "@hiero-ledger/sdk";
const provider = new LocalProvider();
const wallet = new Wallet(process.env.OPERATOR_ID, process.env.OPERATOR_KEY, provider);使用签名者模式时,需用、、和替代 / 。
freezeWithSigner(wallet)signWithSigner(wallet)executeWithSigner(wallet)getReceiptWithSigner(wallet)freezeWith(client)execute(client)Transaction Lifecycle
交易生命周期
Every transaction follows this flow: configure -> freeze -> sign -> execute -> receipt.
js
const tx = await new SomeTransaction()
.setSomeField(value)
.freezeWith(client); // locks the transaction body
await tx.sign(privateKey); // add signatures (call multiple times for multi-sig)
const response = await tx.execute(client);
const receipt = await response.getReceipt(client);When only the operator key is needed, you can skip explicit freeze/sign — handles it:
execute(client)js
const response = await new SomeTransaction()
.setSomeField(value)
.execute(client);
const receipt = await response.getReceipt(client);每笔交易均遵循以下流程:配置 -> 冻结 -> 签名 -> 执行 -> 获取回执。
js
const tx = await new SomeTransaction()
.setSomeField(value)
.freezeWith(client); // 锁定交易主体
await tx.sign(privateKey); // 添加签名(多签时可多次调用)
const response = await tx.execute(client);
const receipt = await response.getReceipt(client);若仅需操作员密钥,可跳过显式冻结/签名步骤——会自动处理:
execute(client)js
const response = await new SomeTransaction()
.setSomeField(value)
.execute(client);
const receipt = await response.getReceipt(client);Creating Tokens
创建代币
Fungible Token
可替代代币
js
import {
TokenCreateTransaction, TokenType, TokenSupplyType,
PrivateKey, Hbar,
} from "@hiero-ledger/sdk";
const supplyKey = PrivateKey.generateECDSA();
const { tokenId } = await (
await new TokenCreateTransaction()
.setTokenName("My Token")
.setTokenSymbol("MTK")
.setDecimals(2)
.setInitialSupply(10000)
.setTreasuryAccountId(operatorId)
.setAdminKey(operatorKey)
.setSupplyKey(supplyKey)
.execute(client)
).getReceipt(client);js
import {
TokenCreateTransaction, TokenType, TokenSupplyType,
PrivateKey, Hbar,
} from "@hiero-ledger/sdk";
const supplyKey = PrivateKey.generateECDSA();
const { tokenId } = await (
await new TokenCreateTransaction()
.setTokenName("My Token")
.setTokenSymbol("MTK")
.setDecimals(2)
.setInitialSupply(10000)
.setTreasuryAccountId(operatorId)
.setAdminKey(operatorKey)
.setSupplyKey(supplyKey)
.execute(client)
).getReceipt(client);NFT (Non-Fungible Token)
NFT(非同质化代币)
NFTs require . They have no decimals and no initial supply — you mint individual serials after creation.
TokenType.NonFungibleUniquejs
const { tokenId: nftId } = await (
await new TokenCreateTransaction()
.setTokenName("My NFT Collection")
.setTokenSymbol("MNFT")
.setTokenType(TokenType.NonFungibleUnique)
.setSupplyType(TokenSupplyType.Finite)
.setMaxSupply(1000)
.setTreasuryAccountId(treasuryId)
.setAdminKey(adminKey)
.setSupplyKey(supplyKey)
.execute(client)
).getReceipt(client);NFT需设置,它们没有小数位且无初始供应量——创建后需单独铸造每个序列号。
TokenType.NonFungibleUniquejs
const { tokenId: nftId } = await (
await new TokenCreateTransaction()
.setTokenName("My NFT Collection")
.setTokenSymbol("MNFT")
.setTokenType(TokenType.NonFungibleUnique)
.setSupplyType(TokenSupplyType.Finite)
.setMaxSupply(1000)
.setTreasuryAccountId(treasuryId)
.setAdminKey(adminKey)
.setSupplyKey(supplyKey)
.execute(client)
).getReceipt(client);Minting
铸造
js
import { TokenMintTransaction } from "@hiero-ledger/sdk";
// Fungible — specify amount
await new TokenMintTransaction()
.setTokenId(tokenId)
.setAmount(500)
.execute(client);
// NFT — specify metadata per serial
const { serials } = await (
await new TokenMintTransaction()
.setTokenId(nftId)
.addMetadata(Buffer.from("ipfs://QmFirst..."))
.addMetadata(Buffer.from("ipfs://QmSecond..."))
.execute(client)
).getReceipt(client);
// serials = [Long(1), Long(2)]js
import { TokenMintTransaction } from "@hiero-ledger/sdk";
// 可替代代币——指定数量
await new TokenMintTransaction()
.setTokenId(tokenId)
.setAmount(500)
.execute(client);
// NFT——为每个序列号指定元数据
const { serials } = await (
await new TokenMintTransaction()
.setTokenId(nftId)
.addMetadata(Buffer.from("ipfs://QmFirst..."))
.addMetadata(Buffer.from("ipfs://QmSecond..."))
.execute(client)
).getReceipt(client);
// serials = [Long(1), Long(2)]Token Association
代币关联
Hedera accounts must associate with a token before they can receive it (unless the account has automatic token associations enabled via ).
setMaxAutomaticTokenAssociationsjs
import { TokenAssociateTransaction } from "@hiero-ledger/sdk";
await (
await (
await new TokenAssociateTransaction()
.setAccountId(recipientId)
.setTokenIds([tokenId])
.freezeWith(client)
).sign(recipientKey) // account owner must sign
).execute(client);Alternatively, set on account creation to allow unlimited auto-association.
setMaxAutomaticTokenAssociations(-1)Hedera账户在接收代币前必须关联该代币(除非账户通过启用了自动代币关联)。
setMaxAutomaticTokenAssociationsjs
import { TokenAssociateTransaction } from "@hiero-ledger/sdk";
await (
await (
await new TokenAssociateTransaction()
.setAccountId(recipientId)
.setTokenIds([tokenId])
.freezeWith(client)
).sign(recipientKey) // 账户所有者必须签名
).execute(client);或者,在创建账户时设置以允许无限自动关联。
setMaxAutomaticTokenAssociations(-1)Transfers
转移
Use for both fungible tokens and NFTs. Amounts are signed: negative debits, positive credits. They must net to zero per token.
TransferTransactionjs
import { TransferTransaction } from "@hiero-ledger/sdk";
// Fungible transfer
await new TransferTransaction()
.addTokenTransfer(tokenId, senderId, -100)
.addTokenTransfer(tokenId, receiverId, 100)
.execute(client);
// NFT transfer (tokenId, serial, sender, receiver)
await new TransferTransaction()
.addNftTransfer(nftId, 1, senderId, receiverId)
.execute(client);
// Mix Hbar + token transfers in one transaction
await new TransferTransaction()
.addHbarTransfer(senderId, new Hbar(-5))
.addHbarTransfer(receiverId, new Hbar(5))
.addTokenTransfer(tokenId, senderId, -50)
.addTokenTransfer(tokenId, receiverId, 50)
.execute(client);使用处理可替代代币和NFT的转移。金额为带符号值:负数表示扣除,正数表示增加。每种代币的总金额必须为零。
TransferTransactionjs
import { TransferTransaction } from "@hiero-ledger/sdk";
// 可替代代币转移
await new TransferTransaction()
.addTokenTransfer(tokenId, senderId, -100)
.addTokenTransfer(tokenId, receiverId, 100)
.execute(client);
// NFT转移(tokenId、序列号、发送方、接收方)
await new TransferTransaction()
.addNftTransfer(nftId, 1, senderId, receiverId)
.execute(client);
// 在一笔交易中混合Hbar与代币转移
await new TransferTransaction()
.addHbarTransfer(senderId, new Hbar(-5))
.addHbarTransfer(receiverId, new Hbar(5))
.addTokenTransfer(tokenId, senderId, -50)
.addTokenTransfer(tokenId, receiverId, 50)
.execute(client);Key Roles
密钥角色
Each key is optional. If omitted at creation, that capability is permanently disabled.
| Key | Purpose |
|---|---|
| Update/delete the token; rotate other keys |
| Mint and burn |
| Freeze/unfreeze accounts from transferring this token |
| Grant/revoke KYC status on accounts |
| Wipe token balance from an account |
| Pause/unpause all token operations globally |
| Update the custom fee schedule |
| Update token or NFT metadata |
每个密钥都是可选的。若创建时未设置,对应的功能将永久禁用。
| 密钥 | 用途 |
|---|---|
| 更新/删除代币;轮换其他密钥 |
| 铸造和销毁代币 |
| 冻结/解冻账户的代币转移权限 |
| 授予/撤销账户的KYC状态 |
| 清零账户的代币余额 |
| 全局暂停/恢复所有代币操作 |
| 更新自定义费用方案 |
| 更新代币或NFT元数据 |
Compliance Operations
合规操作
js
// Grant KYC (required when token has kycKey)
await new TokenGrantKycTransaction()
.setTokenId(tokenId).setAccountId(accountId).execute(client);
// Freeze an account
await new TokenFreezeTransaction()
.setTokenId(tokenId).setAccountId(accountId).execute(client);
// Unfreeze
await new TokenUnfreezeTransaction()
.setTokenId(tokenId).setAccountId(accountId).execute(client);
// Wipe tokens from an account
await new TokenWipeTransaction()
.setTokenId(tokenId).setAccountId(accountId).setAmount(10).execute(client);
// Pause all transfers
await new TokenPauseTransaction().setTokenId(tokenId).execute(client);
// Unpause
await new TokenUnpauseTransaction().setTokenId(tokenId).execute(client);js
// 授予KYC权限(当代币设置了kycKey时必需)
await new TokenGrantKycTransaction()
.setTokenId(tokenId).setAccountId(accountId).execute(client);
// 冻结账户
await new TokenFreezeTransaction()
.setTokenId(tokenId).setAccountId(accountId).execute(client);
// 解冻账户
await new TokenUnfreezeTransaction()
.setTokenId(tokenId).setAccountId(accountId).execute(client);
// 清零账户代币
await new TokenWipeTransaction()
.setTokenId(tokenId).setAccountId(accountId).setAmount(10).execute(client);
// 暂停所有转移操作
await new TokenPauseTransaction().setTokenId(tokenId).execute(client);
// 恢复转移操作
await new TokenUnpauseTransaction().setTokenId(tokenId).execute(client);Burning & Deleting
销毁与删除
js
import { TokenBurnTransaction, TokenDeleteTransaction } from "@hiero-ledger/sdk";
// Burn fungible
await new TokenBurnTransaction()
.setTokenId(tokenId).setAmount(100).execute(client);
// Burn NFT serials
await new TokenBurnTransaction()
.setTokenId(nftId).setSerials([1, 2]).execute(client);
// Delete entire token (requires adminKey)
await new TokenDeleteTransaction()
.setTokenId(tokenId).execute(client);js
import { TokenBurnTransaction, TokenDeleteTransaction } from "@hiero-ledger/sdk";
// 销毁可替代代币
await new TokenBurnTransaction()
.setTokenId(tokenId).setAmount(100).execute(client);
// 销毁NFT序列号
await new TokenBurnTransaction()
.setTokenId(nftId).setSerials([1, 2]).execute(client);
// 删除整个代币(需要adminKey)
await new TokenDeleteTransaction()
.setTokenId(tokenId).execute(client);Querying Token Info
查询代币信息
js
import { TokenInfoQuery, TokenNftInfoQuery, NftId } from "@hiero-ledger/sdk";
const info = await new TokenInfoQuery().setTokenId(tokenId).execute(client);
console.log(info.name, info.symbol, info.totalSupply.toString());
const nftInfo = await new TokenNftInfoQuery()
.setNftId(new NftId(nftId, 1))
.execute(client);
console.log(nftInfo.accountId.toString()); // current ownerjs
import { TokenInfoQuery, TokenNftInfoQuery, NftId } from "@hiero-ledger/sdk";
const info = await new TokenInfoQuery().setTokenId(tokenId).execute(client);
console.log(info.name, info.symbol, info.totalSupply.toString());
const nftInfo = await new TokenNftInfoQuery()
.setNftId(new NftId(nftId, 1))
.execute(client);
console.log(nftInfo.accountId.toString()); // 当前所有者Airdrops
空投
Airdrops distribute tokens to multiple recipients. If a recipient can't auto-associate, the airdrop becomes pending and must be claimed.
js
import {
TokenAirdropTransaction, TokenClaimAirdropTransaction,
TokenCancelAirdropTransaction, TokenRejectTransaction, NftId,
} from "@hiero-ledger/sdk";
// Send airdrop
const record = await (
await (
await new TokenAirdropTransaction()
.addTokenTransfer(tokenId, treasuryId, -300)
.addTokenTransfer(tokenId, recipient1, 100)
.addTokenTransfer(tokenId, recipient2, 100)
.addTokenTransfer(tokenId, recipient3, 100)
.addNftTransfer(nftId, 1, treasuryId, recipient1)
.freezeWith(client)
.sign(treasuryKey)
).execute(client)
).getRecord(client);
// Check pending airdrops
const { newPendingAirdrops } = record;
// Recipient claims a pending airdrop
await (
await new TokenClaimAirdropTransaction()
.addPendingAirdropId(newPendingAirdrops[0].airdropId)
.freezeWith(client)
.sign(recipientKey)
).execute(client);
// Sender cancels a pending airdrop
await new TokenCancelAirdropTransaction()
.addPendingAirdropId(newPendingAirdrops[1].airdropId)
.execute(client);
// Recipient rejects tokens they already received
await (
await new TokenRejectTransaction()
.setOwnerId(recipientId)
.addTokenId(tokenId) // reject fungible
.addNftId(new NftId(nftId, 1)) // reject NFT
.freezeWith(client)
.sign(recipientKey)
).execute(client);空投用于向多个接收方分发代币。若接收方无法自动关联,空投将变为待处理状态,需手动领取。
js
import {
TokenAirdropTransaction, TokenClaimAirdropTransaction,
TokenCancelAirdropTransaction, TokenRejectTransaction, NftId,
} from "@hiero-ledger/sdk";
// 发送空投
const record = await (
await (
await new TokenAirdropTransaction()
.addTokenTransfer(tokenId, treasuryId, -300)
.addTokenTransfer(tokenId, recipient1, 100)
.addTokenTransfer(tokenId, recipient2, 100)
.addTokenTransfer(tokenId, recipient3, 100)
.addNftTransfer(nftId, 1, treasuryId, recipient1)
.freezeWith(client)
.sign(treasuryKey)
).execute(client)
).getRecord(client);
// 检查待处理空投
const { newPendingAirdrops } = record;
// 接收方领取待处理空投
await (
await new TokenClaimAirdropTransaction()
.addPendingAirdropId(newPendingAirdrops[0].airdropId)
.freezeWith(client)
.sign(recipientKey)
).execute(client);
// 发送方取消待处理空投
await new TokenCancelAirdropTransaction()
.addPendingAirdropId(newPendingAirdrops[1].airdropId)
.execute(client);
// 接收方拒绝已收到的代币
await (
await new TokenRejectTransaction()
.setOwnerId(recipientId)
.addTokenId(tokenId) // 拒绝可替代代币
.addNftId(new NftId(nftId, 1)) // 拒绝NFT
.freezeWith(client)
.sign(recipientKey)
).execute(client);Custom Fees
自定义费用
HTS supports three fee types. See for full details.
references/custom-fees.mdjs
import {
CustomFixedFee, CustomFractionalFee, CustomRoyaltyFee, Hbar,
} from "@hiero-ledger/sdk";
const fixedFee = new CustomFixedFee()
.setFeeCollectorAccountId(collectorId)
.setHbarAmount(new Hbar(1));
const fractionalFee = new CustomFractionalFee()
.setFeeCollectorAccountId(collectorId)
.setNumerator(1).setDenominator(100) // 1%
.setMin(1).setMax(1000);
const royaltyFee = new CustomRoyaltyFee()
.setFeeCollectorAccountId(collectorId)
.setNumerator(5).setDenominator(100) // 5%
.setFallbackFee(
new CustomFixedFee().setHbarAmount(new Hbar(2))
);
await new TokenCreateTransaction()
.setCustomFees([fixedFee, fractionalFee])
// ... other fields
.execute(client);HTS支持三种费用类型,详情请参阅。
references/custom-fees.mdjs
import {
CustomFixedFee, CustomFractionalFee, CustomRoyaltyFee, Hbar,
} from "@hiero-ledger/sdk";
const fixedFee = new CustomFixedFee()
.setFeeCollectorAccountId(collectorId)
.setHbarAmount(new Hbar(1));
const fractionalFee = new CustomFractionalFee()
.setFeeCollectorAccountId(collectorId)
.setNumerator(1).setDenominator(100) // 1%
.setMin(1).setMax(1000);
const royaltyFee = new CustomRoyaltyFee()
.setFeeCollectorAccountId(collectorId)
.setNumerator(5).setDenominator(100) // 5%
.setFallbackFee(
new CustomFixedFee().setHbarAmount(new Hbar(2))
);
await new TokenCreateTransaction()
.setCustomFees([fixedFee, fractionalFee])
// ... 其他字段
.execute(client);Common Gotchas
常见注意事项
-
Association before transfer: Recipients must associate with the token or have auto-association slots. Without it, transfers fail with.
TOKEN_NOT_ASSOCIATED_TO_ACCOUNT -
Signed amounts:uses signed amounts. Sender gets negative, receiver gets positive. They must net to zero.
addTokenTransfer -
NFTs have no decimals or initial supply: Setand mint serials individually.
TokenType.NonFungibleUnique -
Keys are permanent if no adminKey: Without an admin key, you cannot update or delete the token, and cannot change any other keys.
-
KYC gate: If a token has a, accounts must receive KYC approval before they can transact with that token.
kycKey -
Supply key for mint/burn: You need a supply key set at creation to later mint or burn.
-
Treasury auto-association: The treasury account is automatically associated with the token.
-
Freeze before multi-sig: When multiple parties need to sign, callfirst, then chain
freezeWith(client)calls..sign(key)
- 转移前需关联:接收方必须关联代币或拥有自动关联额度,否则转移会因失败。
TOKEN_NOT_ASSOCIATED_TO_ACCOUNT - 带符号金额:使用带符号金额,发送方为负数,接收方为正数,且每种代币的总金额必须为零。
addTokenTransfer - NFT无小数位和初始供应量:需设置,并单独铸造每个序列号。
TokenType.NonFungibleUnique - 无adminKey时密钥不可更改:若未设置adminKey,无法更新或删除代币,也无法修改其他密钥。
- KYC验证要求:若代币设置了,账户必须获得KYC批准才能进行代币交易。
kycKey - 铸造/销毁需supplyKey:创建代币时需设置supplyKey,后续才能进行铸造或销毁操作。
- 国库账户自动关联:国库账户会自动与代币关联。
- 多签前需冻结:当需要多方签名时,先调用,再链式调用
freezeWith(client)。.sign(key)
Reference Files
参考文件
- — Complete list of all HTS transaction and query classes with their methods
references/api-reference.md - — Detailed guide to fixed, fractional, and royalty fees
references/custom-fees.md
- — 所有HTS交易和查询类及其方法的完整列表
references/api-reference.md - — 固定费用、比例费用和版税费用的详细指南
references/custom-fees.md